[C++]STL知用篇之容器
前言
容器,置物之所也。
一个容器就是一些特定类型对象的集合。根据“数据在容器中的排列”特性,可将容器分为序列式(sequence)和关联式(associative)两种。
一、顺序容器
序列式容器/顺序容器(sequential container)——保存相同类型对象有序集合的类型,顺序容器中元素可以通过其位置来访问,其特点是元素可序(ordered)但未必有序(sorted)。
1.1顺序容器概述
顺序容器类型 | 容器描述 |
---|---|
vector | 可变大小数组,支持快速随机访问,尾部之外位置插入或删除元素可能很慢 |
deque | 双端队列,支持快速随机访问,头尾插入/删除速度很快 |
list | 双向链表,只支持双向顺序访问,在任何位置插入或删除速度都很快 |
forward_list | 单向链表,只支持单向顺序访问,在任何位置插入或删除元素速度都很快 |
array | 数组,支持快速随机访问,不能添加或删除元素 |
string | 字符串,与vector类似,但专门用于存储字符,随机访问块,在尾部插入/删除很快 |
- string与vector都是将元素保存在连续的内存空间中,添加元素时可能需要分配新的存储空间,再将元素移动到新的存储空间中。
- list与forward_list的设计目的是使得在任何位置添加或删除元素很快速,但作为代价,两个容器不支持随机访问
- deque支持快速随机访问,在两端添加/删除元素很快,但在中间位置添加或删除元素可能很慢。
1.1.1 迭代器
标准容器类型上所有的迭代器都允许访问容器中的元素,访问的方式是通过解引用运算符来实现的,标准库容器的所有迭代器都定义了递增运算符。
获取迭代器 | 描述 |
---|---|
c.begin() , c.end() | 返回指向容器c首元素和尾元素之后的迭代器 |
c.cbegin(), c.cend() | 返回const_iterator |
c.rbegin() , c.rend() | 返回指向容器c尾元素和首元素之前的迭代器 |
c.crbegin(), c.crend() | 返回const_reverse_iterator |
迭代器范围:[begin, end)——左开右闭
1.1.2 容器的构造与初始化
容器的构造与初始化 | 描述 |
---|---|
C c | 默认构造函数。如果容器为array,按元素默认方式初始化;否则c为空 |
C c1(c2) | c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致) |
C c1 = c2 | c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致) |
C c{a, b, c …} | 列表初始化,列表中元素类型必须与C的元素类型相容。 |
C c = {a, b, c …} | 列表初始化,列表中元素类型必须与C的元素类型相容。 |
C c(b, e) | c初始化为迭代器b和e指定范围间元素的拷贝,范围内的元素类型必须与C的元素类型相容 |
顺序容器(不包含array)接受大小参数的构造函数 | 描述 |
---|---|
C seq(n) | seq包含n个元素,这些元素进行了值初始化(string不适用) |
C seq(n, t) | seq包含n个初始化为t的元素 |
1.1.3 赋值与swap
容器的赋值 | 描述 |
---|---|
c1 = c2 | 将c1中的元素替换为c2中元素的拷贝,c1与c2必须具有相同的类型 |
c = {a, b, c …} | 将c1中的元素替换为初始化列表中元素的拷贝(array不适用) |
swap(c1, c2) | 交换c1与c2的元素,二者必须有相同的元素类型 |
c1.swap(c2) | 交换c1与c2的元素,二者必须有相同的元素类型 |
assign不适用于关联容器和array
assign | 描述 |
---|---|
seq.assign(b, e) | 将seq中的元素替换为迭代器b与e所表示范围中的元素,迭代器b与e不能指向seq中的元素 |
seq.assign(il) | 将seq中的元素替换为初始化列表il中的元素 |
seq.assign(n, t) | 将seq中的元素替换为n个值为t的元素 |
1.1.4 容器大小操作
- size():返回容器中元素数目
- max_size():返回容器可保存最大元素数目
- empty():若c中存储了元素返回false,否则返回true
- capacity():不分配新的内存空间的前提下最多可以保存多少元素
关系运算符:
每个容器类型都支持(==与!=),除了无序关联容器外的所有容器都支持关系运算符(>, <, >=, <=)。比较两个容器实际上是逐元素比较。
- 若两个容器大小相同且所有元素两两对应相等,则两个容器相等,反之不等
- 若两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器
- 若两个容器都不是另一个容器的前缀子序列,则比较结果取决于第一个不相等元素的比较结果
改变容器大小:
可以使用resize增大或缩小容器(array不适用)。如果当前大小大于所要求的大小,容器后部的元素会被删除;如果当前大小小于新的大小,会将新元素添加到容器后部。
resize | 描述 |
---|---|
c.resize(n) | 调整c大小为n个元素,若n < c.size(),则多出的元素被丢弃;若必须添加新元素,对新元素值进行初始化 |
c.resize(n, t) | 调整c大小为n个元素。任何新添加的元素初始化为t |
1.1.5 顺序容器操作
添加元素:
添加元素操作 | 描述 |
---|---|
c.push_back(t) | 在c尾部创建一个值为t的元素,返回void |
c.emplace_back(args) | 在c尾部创建一个由args创建的元素,返回void |
c.push_front(t) | 在c头部创建一个值为t的元素,返回void |
c.emplace_front(args) | 在c头部创建一个由args创建的元素,返回void |
c.insert(p, t) | 在迭代器p指向元素之前创建一个值为t的元素,返回指向新添加元素的迭代器; |
c.emplace(p, args) | 在迭代器p指向元素之前创建一个由args创建的的元素,返回指向新添加元素的迭代器; |
c.insert(p, n, t) | 在迭代器p指向元素之前创建n个值为t的元素,返回指向新添加的第一个元素的迭代器;若n为0返回p |
c.insert(p, b, e) | 将迭代器b与e指定范围内元素插入p指向元素之前,b与e不能指向c中的元素,返回新添加第一个元素的迭代器,若范围为空返回p |
c.insert(p, il) | il是一个花括号包围的元素值列表。操作将这些元素插入p指向元素之前,返回新添加第一个元素的迭代器,若列表为空返回p |
注意:
- 向一个vector、string或deque插入元素会使得所有指向容器的迭代器、引用、指针失效
- 容器元素是拷贝。但使用对象初始化容器或将一个对象插入容器时,实际上放入容器中的是对象值的一个拷贝,容器中的元素与提供值的对象之间没有任何关联。随后对容器中元素的任何改变不会影响到原始对象,反之亦然。
访问元素:
如果容器中没有元素,访问操作的结果是未定义的。
访问元素操作 | 描述 |
---|---|
c.back() | 返回尾元素的引用 |
c.front() | 返回首元素的引用 |
c[n] | 返回c中下标为n的元素的引用 |
c.at(n) | 返回下标为n的元素的引用 |
注意:
- at和下标操作只适用于string、vector、deque和array
- 访问成员函数(back, front, at 和下标)返回的是引用
删除元素:
删除元素操作 | 描述 |
---|---|
c.pop_back() | 删除c中尾元素,返回void |
c.pop_front() | 删除c中首元素,返回void |
c.erase( p) | 删除迭代器p所指向的元素,返回一个指向被删除元素之后元素的迭代器;若p指向尾元素,返回尾迭代器;若p是尾后迭代器,则函数行为未定义 |
c.erase(b,e) | 删除迭代器b与e所指定范围内的元素。返回指向最后一个被删除元素之后元素的迭代器;若e本身是尾后迭代器,则函数返回尾后迭代器 |
c.clear() | 删除c中所有元素,返回void |
注意:
- 上述操作改变容器大小,不适合array
- forward_list有特殊版本erase,且不适合pop_back
- vector、string不支持pop_front
注意
容器操作可能使迭代器失效
向容器中添加或删除元素都可能使得指向容器元素的指针、引用和迭代器失效,一个失效的指针、引用或迭代器将不再表示任何元素
1.添加元素
- 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针、引用都会失效;如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针、引用仍然有效,但是插入位置之后的将会失效。
- 对于deque,插入到除首尾位置之外的任何位置将会导致迭代器、引用、指针失效。如果在首尾位置添加元素,则迭代器会失效,而指向存在的元素的引用和指针不会失效。
- 对于list或forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针、引用仍然有效。
2.删除元素(指向被删除元素的迭代器、指针、引用显然失效,注意:删除元素后尾后迭代器总是失效的)
- 对于list或forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、指针、引用仍然有效。
- 对于deque,如果在首尾之外的任何位置删除元素,指向被删除元素之外的其它元素的迭代器、指针、引用也会失效;若删除的是尾元素,则尾后迭代器失效,其它迭代器、引用、指针不受影响;如果删除的是首元素,这些也不会受影响。
- 对于vector和string,指向被删除元素之前的迭代器、指针、引用仍有效。
1.2 vector
- vectors是可变大小数组,支持快速随机访问,在尾部之外的位置插入或删除元素可能很慢。
- vector是动态空间,随着元素的加入,其内部机制会自行扩充空间来容纳新元素。为了降低空间配置的速度成本,vector实际配置空间大小比客户端需求的更大一些,以备将来可能的扩充。扩充的具体操作是当vector备用空间不足时,配置一块原大小两倍的空间,将原内容拷贝到新的空间并释放旧空间(配置新空间、复制数据、释放旧空间)
1.2.1 vector构造与初始化
- C c:默认构造函数。如果容器为array,按元素默认方式初始化;否则c为空
- C c1(c2): c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
- C c1 = c2:c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
- C c{a, b, c …}|:列表初始化,列表中元素类型必须与C的元素类型相容。
- C c = {a, b, c …}: 列表初始化,列表中元素类型必须与C的元素类型相容。
- C c(b, e):c初始化为迭代器b和e指定范围间元素的拷贝,范围内的元素类型必须与C的元素类型相容
- C seq(n):seq包含n个元素,这些元素进行了值初始化
- C seq(n, t) : seq包含n个初始化为t的元素
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
//默认构造函数构造一个空的容器
vector<string> v1;
//将一个容器初始化为另一个容器的拷贝(注意两个容器类型与元素类型必须匹配)
list<string> l_test = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};
vector<const char*> v_test1 = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};
vector<string> v_test2 = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};
vector<string> v2(l_test);//error,容器类型不匹配
vector<string> v2(v_test1);//error,容器中元素类型不匹配
vector<string> v2(v_test2);//正确,容器类型与元素类型均匹配
//列表初始化
vector<string> v3 = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};
//与顺序容器大小相关的构造函数
vector<int> v4(3, 9);//初始化有三个int元素,其值均为9
vector<int> v5(10);//10个元素,初始化为0
vector<string> v6(10);//10个元素,每个都为空的string
//容器的容器
vector<vector<string>> v7;//v7中的元素类型是string类型的vector
1.2.2 vector相关元素操作
- v.push_back():将新元素插入vector尾部
- v.pop_back():将尾端元素拿掉并调整大小(尾部标记前移一位)
- v.erase(iterator):清除某位置上元素
- v.insert(iterator, value):在某个位置插入元素
- v.clear(): 清除容器中所有元素
测试代码如下:
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
vector<string> friends;
cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;//0 0
//尾部插入元素
friends.push_back("Chandler");
friends.push_back("Monica");
friends.push_back("Joey");
friends.push_back("Rachel");
friends.push_back("Ross");
friends.push_back("Phoebe");
//遍历元素
vector<string>::iterator ite;
for (ite = friends.begin(); ite != friends.end(); ite++) {
cout << *ite << " ";
}//Chandler Monica Joey Rachel Ross Phoebe
cout << endl;
//测试size、capacity
//尾部弹出元素
friends.push_back("Alan");
friends.push_back("Bob");
friends.push_back("Cindy");
cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 9 9
friends.push_back("David");
cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 10 13(备用空间不足,capacity扩充)
friends.pop_back();
friends.pop_back();
friends.pop_back();
friends.pop_back();
cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 6 13(注意capacity没有减小)
//查找容器中是否含有某个元素
ite = find(friends.begin(), friends.end(), "Chandler");
if (ite != friends.end()) {
cout << *ite << endl; // Chandler
}
//插入元素
ite = find(friends.begin(), friends.end(), "Rachel");
friends.insert(ite, "Alan");
cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 7 13
ite = find(friends.begin(), friends.end(), "Alan");
friends.erase(ite);
cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;// 6 13
//清除容器中所有元素
friends.clear();
cout << "size:" << friends.size() << " capacity:" << friends.capacity() << endl;//0 13(注意:capacity没有改变)
return 0;
}
1.3 list
- list是一个双向链表,其迭代器具有前移、后移功能。list对空间的运用绝对精准,一点儿也不浪费,对于任何位置的元素插入与删除,永远是常数时间。
- list节点在存储空间中不连续存在,不存在类似vector那样备用空间不足时重新配置、数据移动的操作,所以list插入前所有迭代器在插入操作之后仍有效。
1.3.1 list构造与初始化
- C c:默认构造函数。如果容器为array,按元素默认方式初始化;否则c为空
- C c1(c2): c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
- C c1 = c2:c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
- C c{a, b, c …}|:列表初始化,列表中元素类型必须与C的元素类型相容。
- C c = {a, b, c …}: 列表初始化,列表中元素类型必须与C的元素类型相容。
- C c(b, e):c初始化为迭代器b和e指定范围间元素的拷贝,范围内的元素类型必须与C的元素类型相容
- C seq(n):seq包含n个元素,这些元素进行了值初始化
- C seq(n, t) : seq包含n个初始化为t的元素
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
//C c
list<string> ls1;
//C seq(n, t)
list<int> ls2(10, 1);
//C c(b, e)
int arr[] = { 0, 1, 2, 3, 4, 5 };
list<int> ls3(arr, arr + sizeof(arr) / sizeof(arr[0]));
list<int> ls9(ls2.begin(), ls2.end());
//C c1(c2)
//C c1 = c2
list<int> ls4(ls2);
list<int> ls5 = ls2;
//C c{a, b, c...}
//C c = {a, b, c...}
list<string> ls6 = {"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe"};
list<string> ls7{"Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe" };
//C seq(n)
list<string> ls8(6);
return 0;
}
1.3.2 list相关元素操作
- ls.front():返回首部元素
- ls.back():返回尾部元素
- ls.push_front():首部插入元素
- ls.push_back():尾部插入元素
- ls.insert(ite, value):在指定位置插入值为value的元素
- ls.erase(ite):删除指定位置上的元素
- ls.clear():清除所有元素
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
list<string> friends;
cout << friends.empty() << endl;//1
//尾部插入元素
friends.push_back("Chandler");
friends.push_back("Monica");
friends.push_back("Joey");
friends.push_back("Rachel");
friends.push_back("Ross");
friends.push_back("Phoebe");
cout << friends.empty() << endl;//0
cout << "size: " << friends.size() << endl;//6
cout << friends.front() << endl;//Chandler
cout << friends.back() << endl;//Phoebe
//首部插入
friends.push_front("Bob");
cout << friends.front() << endl;//Bob
//遍历元素
list<string>::iterator ite;
for (ite = friends.begin(); ite != friends.end(); ite++) {
cout << *ite << " ";
}//Bob Chandler Monica Joey Rachel Ross Phoebe
cout << endl;
//插入元素
ite = find(friends.begin(), friends.end(), "Monica");
cout << *ite << endl;//Monica
list<string>::iterator ite2 = friends.insert(ite, "Alan");
cout << *ite << " " << *ite2 << endl;// Monica(注意插入元素后原迭代器仍然有效) Alan
//删除元素
friends.erase(ite2);
friends.erase(friends.begin());
cout << friends.size() << endl;//6
//清空
friends.clear();
//交换
list<string> temp{ "hello", "world" };
swap(friends, temp);
for (ite = friends.begin(); ite != friends.end(); ite++) {
cout << *ite << " ";
}//hello world
return 0;
}
1.4 deque
- deque是双端队列,其中的元素可以通过下标访问。deque双端开口,可以分别在头尾两端进行元素的插入和删除,且在两端插入/删除元素不会导致重新分配空间。
- deque没有容量(capacity)的概念,其由一段一段定量连续空间构成,一旦需要在deque头尾两端增加新的空间,便配置一段定量连续空间串接在deque的头端或尾端。
- deque的随机访问元素效率不如vector,对于deque排序操作为了提高效率可先将deque中元素赋值到vector中进行排序,再将排序后结构传入deque。
1.4.1 deque构造与初始化
- C c:默认构造函数。如果容器为array,按元素默认方式初始化;否则c为空
- C c1(c2): c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
- C c1 = c2:c1初始化为c2的拷贝(c1与c2的容器类型与容器内元素类型必须一致,对于array还要求二者大小一致)
- C c{a, b, c …}|:列表初始化,列表中元素类型必须与C的元素类型相容。
- C c = {a, b, c …}: 列表初始化,列表中元素类型必须与C的元素类型相容。
- C c(b, e):c初始化为迭代器b和e指定范围间元素的拷贝,范围内的元素类型必须与C的元素类型相容
- C seq(n):seq包含n个元素,这些元素进行了值初始化
- C seq(n, t) : seq包含n个初始化为t的元素
#include <deque>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
//C c
deque<string> dq1;
//C seq(n, t)
deque<int> dq2(10, 1);
//C c(b, e)
int arr[] = { 0, 1, 2, 3, 4, 5 };
deque<int> dq3(arr, arr + sizeof(arr) / sizeof(arr[0]));
deque<int>::iterator ite;
for (ite = dq3.begin(); ite != dq3.end(); ite++) {
cout << *ite << " ";
}//0 1 2 3 4 5
cout << endl;
deque<int> dq4(dq2.begin(), dq2.end());
for (ite = dq4.begin(); ite != dq4.end(); ite++) {
cout << *ite << " ";
}//1 1 1 1 1 1 1 1 1 1
cout << endl;
//C c1(c2)
//C c1 = c2
deque<int> dq5(dq4);
deque<int> dq6 = dq4;
//C c{a, b, c...}
//C c = {a, b, c...}
deque<string> dq7 = { "Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe" };
deque<string> dq8{ "Chandler", "Monica", "Joey", "Rachel", "Ross", "Phoebe" };
//C seq(n)
deque<int> dq9(6);
for (ite = dq9.begin(); ite != dq9.end(); ite++) {
cout << *ite << " ";
}//0 0 0 0 0 0
return 0;
}
1.4.2 deque相关元素操作
- dq.front():返回首部元素
- dq.back():返回尾部元素
- dq.push_front():首部插入元素
- dq.push_back():尾部插入元素
- dq.pop_front():首部删除元素
- dq.pop_back():尾部删除元素
- dq.insert(ite, value):在指定位置插入值为value的元素
- dq.erase(ite):删除指定位置上的元素
- dq.clear():清除所有元素
#include <deque>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
deque<string> friends;
cout << friends.empty() << endl;//1
//尾部插入元素
friends.push_back("Chandler");
friends.push_back("Monica");
friends.push_back("Joey");
//头部插入元素
friends.push_front("Rachel");
friends.push_front("Ross");
friends.push_front("Phoebe");
cout << friends.empty() << endl;//0
cout << "size: " << friends.size() << endl;//6
cout << friends.front() << endl;//Phoebe
cout << friends.back() << endl;//Joey
friends.push_front("Alan");
cout << friends.front() << endl; //Alan
//头部删除
friends.pop_front();
cout << friends.front() << endl;//Phoebe
friends.push_back("Bob");
cout << friends.back() << endl;//Bob
//尾部删除
friends.pop_back();
cout << friends.back() << endl;//Joey
//遍历元素
deque<string>::iterator ite;
for (ite = friends.begin(); ite != friends.end(); ite++) {
cout << *ite << " ";
}//Phoebe Ross Rachel Chandler Monica Joey
cout << endl;
//通过下标访问元素
for (int i = 0; i < friends.size(); i++) {
cout << friends[i] << " ";
}//Phoebe Ross Rachel Chandler Monica Joey
cout << endl;
//插入元素
ite = find(friends.begin(), friends.end(), "Monica");
cout << *ite << endl;//Monica
deque<string>::iterator ite2 = friends.insert(ite, "Alan");
//cout << *ite << " " << *ite2 << endl;// 注意插入元素后原迭代器失效
//删除元素
friends.erase(ite2);
cout << friends.size() << endl;//6
//清空
friends.clear();
//交换
deque<string> temp{ "hello", "world" };
swap(friends, temp);
for (ite = friends.begin(); ite != friends.end(); ite++) {
cout << *ite << " ";
}//hello world
return 0;
}
1.5 容器适配器
本质上适配器是一种机制,使某种事物的行为看起来像另一种事物。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。
所有容器适配器都支持的操作和类型 | 描述 |
---|---|
size_type | 一种类型,足以保存当前类型最大对象的大小 |
value_type | 元素类型 |
container_type | 实现适配器底层容器类型 |
A a | 创建一个名为a的空适配器 |
A a( c ) | 创建一个名为a的适配器,带有容器c的一个拷贝 |
关系运算符 | ==、!=、>=、<=、>、<;返回底层容器的比较结果 |
a.empty() | a是否为空 |
a.size() | 返回a中元素数量 |
swap(a, b) | 交换a,b内容;a、b必须类型相同且底层容器类型也必须相同 |
a.swap(b) | 交换a,b内容;a、b必须类型相同且底层容器类型也必须相同 |
每个适配器都定义两个构造函数:
- 默认构造函数创建一个新的对象
- 接受一个容器的构造函数拷贝该容器来初始化适配器
1.5.1 stack
- stack是一种先进后出(FIFO)的数据结构,只有顶部一个出口。stack允许新增、移除元素、取得最顶端元素。但是除了最顶端外,没有任何办法可以存取stack的其他元素。stack没有迭代器,不允许有遍历行为。
- stack可以使用deque或list或vector作为底层容器。SGI STL使用deque作为缺省情况下stack的底部结构。
1.5.1.1 stack构造与初始化:
- 默认构造函数创建一个新的对象
- 接受一个容器的构造函数拷贝该容器来初始化适配器(若stack底层容器为deque(list/vector),可以使用一个deque(list/vector)对象初始化stack)。
#include <deque>
#include <stack>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
//A a
stack<int> s1;
deque<int> dq{0,1,2,3,4,5,6,7,8,9};
//A a(c)
//使用deque初始化一个stack对象(底层容器为deque)
stack<int> s2(dq);
while (!s2.empty()) {
cout << s2.top() << " ";
s2.pop();
}//9 8 7 6 5 4 3 2 1 0
//使用vector作为底层容器
vector<int> v1(10, 1);
stack<int, vector<int>> s3(v1);
return 0;
}
1.5.1.2 stack相关元素操作
- s.pop():删除栈顶元素,但不返回元素值
- s.push(item):创建一个新的元素压入栈顶,该元素通过拷贝或移动item而来
- s.emplace(args):创建一个新的元素压入栈顶,该元素通过args构造而来
- s.top():返回栈顶元素,但不将元素弹出栈顶
#include <deque>
#include <stack>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
stack<string> friends;
cout << friends.empty() << endl;//1
//push
friends.push("Chandler");
friends.push("Monica");
friends.push("Joey");
friends.push("Rachel");
friends.push("Ross");
friends.push("Phoebe");
//size、top
cout << friends.size() << endl;//6
cout << friends.top() << endl;//Phoebe
//pop
friends.pop(); cout << friends.top() << endl;//Ross
friends.pop(); cout << friends.top() << endl;//Rachel
friends.pop(); cout << friends.top() << endl;//Joey
return 0;
}
1.5.2 queue
- queue是一种先进先出(FIFO)的数据结构。queue允许新增、移除元素、从最底端加入元素、取得最顶端元素。除了最底端可以加入、最顶端可以取出外,没有其它方法可以存取queue的其他元素。queue没有迭代器,不允许遍历。
- queue使用deque或list作为底层容器。SGI STL使用deque作为缺省情况下queue的底层结构。
1.5.2.1 queue构造与初始化:
- 默认构造函数创建一个新的对象
- 接受一个容器的构造函数拷贝该容器来初始化适配器
#include <deque>
#include <queue>
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
//A a
queue<int> q1;
//A a(c)
deque<int> dq{ 0,1,2,3,4,5,6,7,8,9 };
queue<int> q2(dq);
while (!q2.empty()) {
cout << q2.front() << " ";
q2.pop();
}//0 1 2 3 4 5 6 7 8 9
cout << endl;
//使用list作为底层容器
list<int> l1{ 0,1,2,3,4,5,6,7,8,9 };
queue<int, list<int>> q3(l1);
cout << q3.size() << endl;//10
return 0;
}
1.5.2.2 queue相关元素操作
- q.pop():删除queue首元素但不返回此元素
- q.front():返回首元素但不删除
- q.back():返回尾元素但不删除
- q.push(item):在queue末尾添加一个值为item的元素
- q.emplace(args):在queue末尾添加一个由args创造的元素
#include <deque>
#include <queue>
#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
queue<string> friends;
cout << friends.empty() << endl;//1
//push
friends.push("Chandler");
friends.push("Monica");
friends.push("Joey");
cout << friends.front() << endl;//Chandler
cout << friends.back() << endl;//Joey
friends.push("Rachel");
friends.push("Ross");
friends.push("Phoebe");
//size、top
cout << friends.size() << endl;//6
cout << friends.front() << endl;//Chandler
cout << friends.back() << endl;//Phoebe
//pop
friends.pop(); cout << friends.front() << endl;//Monica
friends.pop(); cout << friends.front() << endl;//Joey
friends.pop(); cout << friends.front() << endl;//Rachel
cout << friends.size() << endl;//3
return 0;
}
1.5.3 priority_queue
- priority_queue是带权值的queue,内部的元素并非按照被推入的次序排列,而是自动依照元素的权值进行排序,权值最高者排在最前面。priority_queue顶端元素(权值最高者)才有机会被外界取用,并且priority_queue没有迭代器也不能进行遍历行为。
- priority_queue缺省情况下以vector为底部容器,利用max-heap实现。
1.5.3.1 priority_queue构造与初始化
priority_queue的构造初始化类似queue。
- 默认构造函数创建一个新的对象
- 接受一个容器的构造函数拷贝该容器来初始化适配器
1.5.3.2 priority_queue相关元素操作
- pq.pop():删除priority_queue最高优先级元素但不返回此元素
- pq.top():返回最高优先级元素,但不删除该元素
- pq.push(item):在priority_queue恰当位置添加一个值为item的元素
- pq.emplace(args):在priority_queue恰当位置添加一个由args创造的元素
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int arr[10] = { 7,3,8,0,2,1,4,9,6,5};
priority_queue<int> pq(arr, arr + 10);
pq.push(22);
pq.push(-3);
cout << pq.size() << endl;//12
while (!pq.empty()) {
cout << pq.top() << " ";
pq.pop();
}//22 9 8 7 6 5 4 3 2 1 0 -3
cout << endl;
return 0;
}
二、关联式容器
- 与顺序容器按照元素在容器中位置来进行顺序保存和访问不同,关联式容器的元素按照关键字来保存和访问,可以支持高效的关键字查找和访问。
- 标准的STL关联式容器分为set(集合)与map(映射表)两大类,以及二者的衍生体multiset(多键集合)和multimap(多键映射表),这些容器底层均使用红黑树实现。
- SGI STL还提供了基于hashtable实现的hash_set、hash_map、hash_multiset、hash_multimap。
关联容器类型:
按关键字有序保存元素 | 描述 |
---|---|
map | 关联数组,保存键值对(key-value) |
set | 关键字即值,即只保存关键字的容器 |
multimap | 关键字可重复出现的map |
multiset | 关键字可重复出现的set |
无序集合 | 描述 |
---|---|
unordered_map | 用哈希函数组织的map |
unordered_set | 用哈希函数组织的set |
unordered_multimap | 哈希组织的map,关键字可重复出现 |
unordered_multiset | 哈希组织的set,关键字可重复出现 |
2.1 pair类型
一个pair可以保存两个数据成员
pair操作 | 描述 |
---|---|
pair<T1, T2> p | p是一个pair,两个类型分别为T1和T2的成员都进行了值初始化 |
pair<T1, T2> p (v1, v2) | p是一个类型成员为T1、T2的pair,first与second成员分别使用v1、v2进行初始化 |
pair<T1, T2> p = {v1, v2} | 等价pair<T1, T2> p (v1, v2) |
make_pair(v1, v2) | 返回使用v1、v2进行初始化进行初始化的pair,pair的类型从v1、v2推断中获得 |
p.first | 返回p的first数据成员 |
p.second | 返回p的second数据成员 |
p1 == p2 | 当first、second成员分别相等时两个pair相等 |
p1 != p2 | 当first、second成员分别相等时两个pair相等,否则不等 |
2.2 set与multiset
set的特点是所有元素会根据元素的键值自动被排序,set元素的key就是value(或者说value就是key),且set不允许两个元素有相同的键值(key)。
当客户端对set进行元素新增或删除操作时,操作之前所有的迭代器操作完成后仍有效(被删除的元素的迭代器除外)
multiset的特性与用法与set完全相同,唯一的差别在于multiset允许键值重复。
#include <set>
#include <iostream>
using namespace std;
int main() {
string arr[6] = { "Chandler","Monica","Joey","Rachel","Ross","Phoebe" };
set<string> friends(arr, arr + 6);
cout << friends.size() << " " << friends.count("Phoebe") << endl;//6 1
//加入已经存在的key
friends.insert("Phoebe");
cout << friends.size() << " " << friends.count("Phoebe") << endl;//6 1
//添加未存在的key
cout << friends.count("Alan") << endl;//0
friends.insert("Alan");
cout << friends.size() << " " << friends.count("Alan") << endl;//7 1
//删除某个key
friends.erase("Alan");
cout << friends.size() << " " << friends.count("Alan") << endl;//6 0
//遍历set
set<string>::iterator ite1 = friends.begin();
set<string>::iterator ite2 = friends.end();
for (; ite1 != ite2; ++ite1) {
cout << *ite1 << " ";
}//Chandler Joey Monica Phoebe Rachel Ross
cout << endl;
//搜索元素(使用STL算法find())
set<string>::iterator ite = find(friends.begin(), friends.end(), "Rachel");
if (ite != friends.end()) {
cout << *ite << endl;//Rachel
}
ite = find(friends.begin(), friends.end(), "Bob");
if (ite != friends.end()) {
cout << *ite << endl;
}//未找到
//搜索元素(使用关联式容器提供的find(),优于使用STL算法find())
ite = friends.find("Rachel");
if (ite != friends.end()) {
cout << *ite << endl;//Rachel
}
//不可以使用迭代器来改变set元素
//*ite = "Bob";//error
return 0;
}
2.3 map与multimap
- map的特点是所有元素会根据元素的键值自动被排序。map中的所有元素都是pair——键值对,同时拥有键值key与实值value。map不允许两个元素有相同的键值key。
- 可以对map中元素的value进行修改但不能对key值进行修改,因为修改key值会严重破坏map组织,而value不影响map元素的排列。
- 客户端对map进行元素新增或删除操作时,操作前所有迭代器操作完成后仍有效(被删除的元素的迭代器会失效)。
- multimap的特性与用法与map完全相同,唯一的差别在于multimap允许出现键值的重复。
#include <map>
#include <iostream>
using namespace std;
int main() {
map<string, int> friends;
friends[string("Chandler")] = 1;
friends[string("Monica")] = 2;
friends[string("Joey")] = 3;
friends[string("Rachel")] = 4;
friends[string("Ross")] = 5;
friends[string("Phoebe")] = 6;
pair<string, int> value1(string("Cindy"), 7);
pair<string, int> value2(string("jjhou"), 8);
//插入元素
friends.insert(value1);
friends.insert(value2);
//遍历map元素
map<string, int>::iterator ite = friends.begin();
for (; ite != friends.end(); ite++) {
cout << ite->first << ": " << ite->second << "; ";
}//Chandler: 1; Cindy: 7; Joey: 3; Monica: 2; Phoebe: 6; Rachel: 4; Ross: 5; jjhou: 8;
cout << endl;
//使用关联式容器的find函数搜索元素
ite = friends.find("cxj");
if (ite != friends.end()) {
cout << ite->first << ": " << ite->second << endl;
}//未找到
ite = friends.find("Ross");
if (ite != friends.end()) {
cout << ite->first << ": " << ite->second << endl;
}//Ross: 5
//修改元素的value
ite->second = 222;
friends[string("Chandler")] = 333;
return 0;
}
2.4 无序关联容器
无序关联容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符。无序容器中元素不具有自动排序功能,使用方式方面与上述容器一致。
总结
最近在阅读侯捷老师的《STL源码剖析》,于是参考着《C++ Primer》对书中容器部分的使用做了一些整理归纳。
注:代码块中注释C代表容器(container)构造函数、A代表容器适配器(container adapter)构造函数
ps:这篇文章是本人第一次尝试用博客的形式记录一些知识点,文章内容、结构方面可能存在较多问题,还望大家批评指正!
参考书籍
1. 《STL源码剖析》
2. 《C++ Primer》