简单的理解容器,它就是一些模板类的集合,但和普通模板类不同的是,容器中封装的是组织数据的方法(也就是数据结构)。STL 提供有 3 类标准容器,分别是序列容器、排序容器和哈希容器,其中后两类容器有时也统称为关联容器。
容器种类 | 功能 |
---|
序列容器 | 主要包括 vector 向量容器、list 列表容器以及deque双端队列容器。之所以被称为序列容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置,元素就会位于什么位置。 | 排序容器 | 包括 **set 集合容器、multiset多重集合容器、map映射容器以及 multimap **多重映射容器。排序容器中的元素默认是由小到大排序好的,即便是插入元素,元素也会插入到适当位置。所以关联容器在查找时具有非常好的性能。 | 哈希容器 | C++ 11 新加入 4 种关联式容器,分别是 unordered_set 哈希集合、 unordered_multiset哈希多重集合、unordered_map 哈希映射以及 unordered_multimap 哈希多重映射。和排序容器不同,哈希容器中的元素是未排序的,元素的位置由哈希函数确定。 |
序列容器
所谓序列容器,即以线性排列(类似普通数组的存储方式)来存储某一指定类型(例如 int、double 等)的数据,需要特殊说明的是,该类容器并不会自动对存储的元素按照值的大小进行排序。
array(数组容器)
array<T,N>(数组容器),表示可以存储 N 个 T 类型的元素,是 C++ 本身提供的一种容器。此类容器一旦建立,其长度就是固定不变的,这意味着不能增加或删除元素,只能改变某个元素的值。
在使用该容器之前,代码中需引入array 头文件,并默认使用 std 命令空间,如下所示:
#include <array>
using namespace std;
在 **array<T,N> **类模板中,T 用于指明容器中的存储的具体数据类型,N 用于指明容器的大小,需要注意的是,这里的 N 必须是常量,不能用变量表示。
初始化方式
创建具有 10 个 double 类型元素的 array 容器:
std::array<double, 10> values;
使用这种方式创建的容器中,各个元素的值是不确定的(array 容器不会做默认初始化操作)。 注意,如果程序中已经默认指定了 std 命令空间,这里可以省略 std::。
std::array<double, 10> values {};
可以将所有的元素初始化为 0 或者和默认元素类型等效的值,使用该语句,容器中所有的元素都会被初始化为 0.0。
std::array<double, 10> values {0.5,1.0,1.5,2.0};
可以像创建常规数组那样对元素进行初始化,这里只初始化了前 4 个元素,剩余的元素都会被初始化为 0.0。
函数说明
函数 | 说明 |
---|
array1.swap(array2) | 交换 array1 和 array2 容器中的所有元素,但前提是它们具有相同的长度和类型。 |
访问元素的几种方式
访问array容器中单个元素
- 通过容器名[]的方式直接访问和使用容器中的元素,
values[4] = values[3] + 2.O*values[1];
使用如上这样方式,由于没有做任何边界检查,所以即便使用越界的索引值去访问或存储元素,也不会被检测到。
- at()成员函数
为了能够有效地避免越界访问的情况,可以使用 array 容器提供的 at() 成员函数,例如 :
values.at (4) = values.at(3) + 2.O*values.at(1);
当传给 at() 的索引是一个越界值时,程序会抛出 std::out_of_range 异常
- get 模板函数
它是一个辅助函数,能够获取到容器的第 n 个元素。需要注意的是,该模板函数中,参数的实参必须是一个在编译时可以确定的常量表达式,所以它不能是一个循环变量。也就是说,它只能访问模板参数指定的元素,编译器在编译时会对它进行检查。
#include <iostream>
#include <array>
#include <string>
using namespace std;
int main()
{
array<string, 5> words{ "one","two","three","four","five" };
cout << get<3>(words) << endl;
return 0;
}
- data() 成员函数
通过调用该函数可以得到指向容器首个元素的指针。通过该指针,我们可以获得容器中的各个元素,例如:
#include <iostream>
#include <array>
using namespace std;
int main()
{
array<int, 5> words{1,2,3,4,5};
cout << *( words.data()+1);
return 0;
}
访问array容器中多个元素
- size() 函数
size() 函数能够返回容器中元素的个数(函数返回值为 size_t 类型),所以能够像下面这样去逐个提取容器中的元素,并计算它们的和:
double total = 0;
for(size_t i = 0 ; i < values.size() ; ++i)
{
total += values[i];
}
- 使用基于范围的循环,因此能够更加简便地计算容器中所有元素的和,比如:
double total = 0;
for(auto&& value : values)
total += value;
#include <iostream>
#include <iomanip>
#include <array>
using namespace std;
int main()
{
array<int, 5> values1;
array<int, 5> values2;
for (size_t i = 0; i < values1.size(); ++i)
{
values1.at(i) = i;
}
cout << "values1[0] is : " << values1[0] << endl;
cout << "values1[1] is : " << values1.at(1) << endl;
cout << "values1[2] is : " << get<2>(values1) << endl;
int initvalue = 10;
for (auto& value : values2)
{
value = initvalue;
initvalue++;
}
cout << "Values1 is : ";
for (auto i = values1.begin(); i < values1.end(); i++) {
cout << *i << " ";
}
cout << endl << "Values2 is : ";
for (auto i = values2.begin(); i < values2.end(); i++) {
cout << *i << " ";
}
return 0;
}
运行结果为
values1[0] is : 0
values1[1] is : 1
values1[2] is : 2
Values1 is : 0 1 2 3 4
Values2 is : 10 11 12 13 14
示例
#include <iostream>
#include <array>
using namespace std;
int main()
{
std::array<int, 4> values{};
for (int i = 0; i < values.size(); i++) {
values.at(i) = i;
}
cout << get<3>(values) << endl;
if (!values.empty()) {
for (auto val = values.begin(); val < values.end(); val++) {
cout << *val << " ";
}
}
}
代码中的 auto 关键字,可以使编译器自动判定变量的类型。
vector(向量容器)
vector(向量容器),用来存放 T 类型的元素,是一个长度可变的序列容器,即在存储空间不足时,会自动申请更多的内存。使用此容器,在尾部增加或删除元素的效率最高(时间复杂度为 O(1) 常数阶),在其它位置插入或删除元素效率较差(时间复杂度为 O(n) 线性阶,其中 n 为容器中元素的个数)。
#include <vector>
using namespace std;
初始化方式
- 如下代码展示了如何创建存储 double 类型元素的一个 vector 容器:
std::vector<double> values;
这是一个空的 vector 容器,因为容器中没有元素,所以没有为其分配空间。当添加第一个元素(比如使用 push_back() 函数)时,vector 会自动分配内存。 在创建好空容器的基础上,还可以像下面这样通过调用 reserve() 成员函数来增加容器的容量:
values.reserve(20);
调用 reserve() 不会影响已存储的元素,也不会生成任何元素,即 values 容器内此时仍然没有任何元素。
- 在创建的同时指定初始值以及元素个数,比如:
std::vector<int> primes {2, 3, 5, 7, 11, 13, 17, 19};
- 在创建 vector 容器时,也可以指定元素个数:
std::vector<double> values(20);
values 容器开始时就有 20 个元素,它们的默认初始值都为 0。 注意, 圆括号 () 和大括号 {} 是有区别的,前者(例如 (20) )表示元素的个数,而后者(例如 {20} ) 则表示 vector 容器中只有一个元素 20。 如果不想用 0 作为默认值,也可以指定一个其它值,例如,
std::vector<double> values(20, 1.0);
圆括号 () 中的 2 个参数,既可以是常量,也可以用变量来表示,例如:
int num=20;
double value =1.0;
std::vector<double> values(num, value);
- 通过存储元素类型相同的其它 vector 容器,也可以创建新的 vector 容器,例如:
std::vector<char>value1(5, 'c');
std::vector<char>value2(value1);
value2 容器中也具有 5 个字符 ‘c’。 如果不想复制其它容器中所有的元素,可以用一对指针或者迭代器来指定初始值的范围,例如:
int array[]={1,2,3};
std::vector<int>values(array, array+2);
std::vector<int>value1{1,2,3,4,5};
std::vector<int>value2(std::begin(value1),std::begin(value1)+3);
value2 容器中就包含了 {1,2,3} 这 3 个元素。
vector 容器还有一个 std::swap(x , y) 非成员函数(其中 x 和 y 是存储相同类型元素的 vector 容器),它和 swap() 成员函数的功能完全相同,仅使用语法上有差异。 示例
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<char>value;
value.push_back('S');
value.push_back('T');
value.push_back('L');
printf("元素个数为:%d\n", value.size());
for (auto i = value.begin(); i < value.end(); i++) {
cout << *i << " ";
}
cout << endl;
value.insert(value.begin(), 'C');
cout << "首个元素为:" << value.at(0) << endl;
return 0;
}
元素个数为:3 S T L 首个元素为:C
访问元素的几种方式
访问vector容器中单个元素
- vector 容器可以向普通数组那样访问存储的元素,甚至对指定下标处的元素进行修改,比如:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> values{1,2,3,4,5};
cout << values[0] << endl;
values[0] = values[1] + values[2] + values[3] + values[4];
cout << values[0] << endl;
return 0;
}
容器名[n]这种获取元素的方式,需要确保下标 n 的值不会超过容器的容量(可以通过 capacity() 成员函数获取),否则会发生越界访问的错误。
- at() 成员函数
当传给 at() 的索引会造成越界时,会抛出std::out_of_range异常。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> values{1,2,3,4,5};
cout << values.at(0) << endl;
values.at(0) = values.at(1) + values.at(2) + values.at(3) + values.at(4);
cout << values.at(0) << endl;
return 0;
}
- front() 和 back(),它们分别返回 vector 容器中第一个和最后一个元素的引用,通过利用这 2 个函数返回的引用,可以访问(甚至修改)容器中的首尾元素。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> values{1,2,3,4,5};
cout << "values 首元素为:" << values.front() << endl;
cout << "values 尾元素为:" << values.back() << endl;
values.front() = 10;
cout <<"values 新的首元素为:" << values.front() << endl;
values.back() = 20;
cout << "values 新的尾元素为:" << values.back() << endl;
return 0;
}
输出结果为:
values 首元素为:1
values 尾元素为:5
values 新的首元素为:10
values 新的尾元素为:20
- data() 成员函数
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> values{1,2,3,4,5};
cout << *(values.data() + 2) << endl;
*(values.data() + 1) = 10;
cout << *(values.data() + 1) << endl;
return 0;
}
访问vector容器中多个元素
- 借助 size() 成员函数,该函数可以返回 vector 容器中实际存储的元素个数。例如:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> values{1,2,3,4,5};
for (int i = 0; i < values.size(); i++) {
cout << values[i] << " ";
}
return 0;
}
1 2 3 4 5 注意, 这里不要使用 capacity() 成员函数,因为它返回的是 vector 容器的容量,而不是实际存储元素的个数,这两者是有差别的。
- 使用基于范围的循环,此方式将会逐个遍历容器中的元素。比如:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> values{1,2,3,4,5};
for (auto&& value : values)
cout << value << " ";
return 0;
}
- 使用 vector 迭代器遍历 vector 容器,这里以 begin()/end() 为例:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> values{1,2,3,4,5};
for (auto first = values.begin(); first < values.end(); ++first) {
cout << *first << " ";
}
return 0;
}
这里也可以使用 rbegin()/rend()、cbegin()/cend()、crbegin()/crend() 以及全局函数 begin()/end() ,它们都可以实现对容器中元素的访问。
添加元素push_back()和emplace_back()
- push_back()
是在 vector 容器尾部添加一个元素,用法也非常简单,比如:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> values{};
values.push_back(1);
values.push_back(2);
for (int i = 0; i < values.size(); i++) {
cout << values[i] << " ";
}
return 0;
}
运行程序,输出结果为:1 2
- emplace_back()
其功能和 push_back() 相同,都是在 vector 容器的尾部添加一个元素。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> values{};
values.emplace_back(1);
values.emplace_back(2);
for (int i = 0; i < values.size(); i++) {
cout << values[i] << " ";
}
return 0;
}
运行结果为:1 2 emplace_back()和push_back()的区别 emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
emplace_back() 的执行效率比 push_back() 高。因此,在实际使用时,建议大家优先选用 emplace_back()。
插入元素insert()和emplace()
insert() 函数的功能是在 vector 容器的指定位置插入一个或多个元素。该函数的语法格式有多种,如表
语法格式 | 用法说明 |
---|
iterator insert(pos,elem) | 在迭代器 pos 指定的位置之前插入一个新元素elem,并返回表示新插入元素位置的迭代器。 | iterator insert(pos,n,elem) | 在迭代器 pos 指定的位置之前插入 n 个元素 elem,并返回表示第一个新插入元素位置的迭代器。 | iterator insert(pos,first,last) | 在迭代器 pos 指定的位置之前,插入其他容器(不仅限于vector)中位于 [first,last) 区域的所有元素,并返回表示第一个新插入元素位置的迭代器。 | iterator insert(pos,initlist) | 在迭代器 pos 指定的位置之前,插入初始化列表(用大括号{}括起来的多个元素,中间有逗号隔开)中所有的元素,并返回表示第一个新插入元素位置的迭代器。 |
#include <iostream>
#include <vector>
#include <array>
using namespace std;
int main()
{
std::vector<int> demo{1,2};
demo.insert(demo.begin() + 1, 3);
demo.insert(demo.end(), 2, 5);
std::array<int,3>test{ 7,8,9 };
demo.insert(demo.end(), test.begin(), test.end());
demo.insert(demo.end(), { 10,11 });
for (int i = 0; i < demo.size(); i++) {
cout << demo[i] << " ";
}
return 0;
}
运行结果为:1 3 2 5 5 7 8 9 10 11 emplace() 是 C++ 11 标准新增加的成员函数,用于在 vector 容器指定位置之前插入一个新的元素。emplace() 每次只能插入一个元素,而不是多个。
iterator emplace (const_iterator pos, args...);
pos 为指定插入位置的迭代器;args… 表示与新插入元素的构造函数相对应的多个参数;该函数会返回表示新插入元素位置的迭代器。
#include <vector>
#include <iostream>
using namespace std;
int main()
{
std::vector<int> demo1{1,2};
demo1.emplace(demo1.begin(), 3);
for (int i = 0; i < demo1.size(); i++) {
cout << demo1[i] << " ";
}
return 0;
}
运行结果为:3 1 2 emplace() 在插入元素时,是在容器的指定位置直接构造元素,而不是先单独生成,再将其复制(或移动)到容器中。因此,在实际使用中,推荐大家优先使用 emplace()。
vector删除元素方法
- pop_back(),删除 vector 容器中最后一个元素,该容器的大小(size)会减 1,但容量(capacity)不会发生改变。
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<int>demo{ 1,2,3,4,5 };
demo.pop_back();
cout << "size is :" << demo.size() << endl;
cout << "capacity is :" << demo.capacity() << endl;
for (int i = 0; i < demo.size(); i++) {
cout << demo[i] << " ";
}
return 0;
}
运行结果为:
size is :4
capacity is :5
1 2 3 4
- erase(pos),删除 vector 容器中 pos 迭代器指定位置处的元素,并返回指向被删除元素下一个位置元素的迭代器。该容器的大小(size)会减 1,但容量(capacity)不会发生改变。
iterator erase (pos);
- pos 为指定被删除元素位置的迭代器,同时该函数会返回一个指向删除元素所在位置下一个位置的迭代器。
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<int>demo{ 1,2,3,4,5 };
auto iter = demo.erase(demo.begin() + 1);
cout << "size is :" << demo.size() << endl;
cout << "capacity is :" << demo.capacity() << endl;
for (int i = 0; i < demo.size(); i++) {
cout << demo[i] << " ";
}
cout << endl << *iter << endl;
return 0;
}
运行结果为:
size is :4
capacity is :5
1 3 4 5
3
erase() 函数在删除元素时,会将删除位置后续的元素陆续前移,并将容器的大小减 1。
- swap(beg)、pop_back() 先调用 swap() 函数交换要删除的目标元素和容器最后一个元素的位置,然后使用 pop_back() 删除该目标元素。如果不在意容器中元素的排列顺序。
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
vector<int>demo{ 1,2,3,4,5 };
swap(*(std::begin(demo)+1),*(std::end(demo)-1));
for (int i = 0; i < demo.size(); i++) {
cout << demo[i] << " ";
}
demo.pop_back();
cout << endl << "size is :" << demo.size() << endl;
cout << "capacity is :" << demo.capacity() << endl;
for (int i = 0; i < demo.size(); i++) {
cout << demo[i] << " ";
}
return 0;
}
运行结果为:
1 5 3 4 2
size is :4
capacity is :5
1 5 3 4
- erase(beg,end) 删除 vector 容器中位于迭代器 [beg,end)指定区域内的所有元素,并返回指向被删除区域下一个位置元素的迭代器。该容器的大小(size)会减小,但容量(capacity)不会发生改变。
iterator erase (iterator first, iterator last);
first 和 last 是指定被删除元素区域的迭代器,同时该函数会返回指向此区域之后一个位置的迭代器。
#include <vector>
#include <iostream>
using namespace std;
int main()
{
std::vector<int> demo{ 1,2,3,4,5 };
auto iter = demo.erase(demo.begin()+1, demo.end() - 2);
cout << "size is :" << demo.size() << endl;
cout << "capacity is :" << demo.capacity() << endl;
for (int i = 0; i < demo.size(); i++) {
cout << demo[i] << " ";
}
return 0;
}
运行结果为: size is :3 capacity is :5 1 4 5
- remove() 删除容器中所有和指定元素值相等的元素,并返回指向最后一个元素下一个位置的迭代器。值得一提的是,调用该函数不会改变容器的大小和容量。
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
vector<int>demo{ 1,3,3,4,3,5 };
auto iter = std::remove(demo.begin(), demo.end(), 3);
cout << "size is :" << demo.size() << endl;
cout << "capacity is :" << demo.capacity() << endl;
for (auto first = demo.begin(); first < iter;++first) {
cout << *first << " ";
}
return 0;
}
运行结果为: size is :6 capacity is :6 1 4 5
由于该函数并没有改变容器原来的大小和容量,因此无法使用之前的方法遍历容器,而是需要向程序中那样,借助 remove() 返回的迭代器完成正确的遍历。 该容器的大小和容量都没有改变,其剩余位置还保留了之前存储的元素。我们可以使用 erase() 成员函数删掉这些 “无用” 的元素。
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
vector<int>demo{ 1,3,3,4,3,5 };
auto iter = std::remove(demo.begin(), demo.end(), 3);
demo.erase(iter, demo.end());
cout << "size is :" << demo.size() << endl;
cout << "capacity is :" << demo.capacity() << endl;
for (int i = 0; i < demo.size();i++) {
cout << demo[i] << " ";
}
return 0;
}
运行结果为:
size is :3
capacity is :6
1 4 5
- clear() 删除 vector 容器中所有的元素,使其变成空的 vector 容器。该函数会改变 vector 的大小(变为 0),但不是改变其容量。
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
vector<int>demo{ 1,3,3,4,3,5 };
demo.clear();
cout << "size is :" << demo.size() << endl;
cout << "capacity is :" << demo.capacity() << endl;
return 0;
}
运行结果为:
size is :0
capacity is :6
deque(双端队列容器)
和 vector 非常相似,区别在于使用该容器不仅尾部插入和删除元素高效,在头部插入或删除元素也同样高效,时间复杂度都是 O(1) 常数阶,但是在容器中某一位置处插入或删除元素,时间复杂度为 O(n) 线性阶。deque 容器中存储元素并不能保证所有元素都存储到连续的内存空间中。 当需要向序列两端频繁的添加或删除元素时,应首选 deque 容器。
#include <deque>
using namespace std;
初始化方式
- 创建一个没有任何元素的空 deque 容器:
std::deque<int> d;
和空 array 容器不同,空的 deque 容器在创建之后可以做添加或删除元素的操作,因此这种简单创建 deque 容器的方式比较常见。
- 创建一个具有 n 个元素的 deque 容器,其中每个元素都采用对应类型的默认值:
std::deque<int> d(10);
创建一个具有 10 个元素(默认都为 0)的 deque 容器。
- 创建一个具有 n 个元素的 deque 容器,并为每个元素都指定初始值,例如:
std::deque<int> d(10, 5)
创建了一个包含 10 个元素(值都为 5)的 deque 容器。
- 在已有 deque 容器的情况下,可以通过拷贝该容器创建一个新的 deque 容器,例如:
std::deque<int> d1(5);
std::deque<int> d2(d1);
注意:采用此方式,必须保证新旧容器存储的元素类型一致。
- 通过拷贝其他类型容器中指定区域内的元素(也可以是普通数组),可以创建一个新容器,例如:
int a[] = { 1,2,3,4,5 };
std::deque<int>d(a, a + 5);
std::array<int, 5>arr{ 11,12,13,14,15 };
std::deque<int>d(arr.begin()+2, arr.end());
成员函数
函数 | 说明 |
---|
emplace() | 在指定的位置直接生成一个元素。 | emplace_front() | 在容器头部生成一个元素。和 push_front() 的区别是,该函数直接在容器头部构造元素,省去了复制移动元素的过程。 | emplace_back() | 在容器尾部生成一个元素。和 push_back() 的区别是,该函数直接在容器尾部构造元素,省去了复制移动元素的过程。 |
删除了 capacity()、reserve() 和 data() 成员函数。 std::swap(x , y) 非成员函数(其中 x 和 y 是存储相同类型元素的 deque 容器),它和 swap() 成员函数的功能完全相同。
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int>d;
d.push_back(1);
d.push_back(2);
d.push_back(3);
d.push_front(0);
printf("元素个数为:%d\n", d.size());
for (auto i = d.begin(); i < d.end(); i++) {
cout << *i << " ";
}
cout << endl;
return 0;
}
运行结果为: 元素个数为:4 0 1 2 3
访问元素的几种方式
- 采用普通数组访问存储元素的方式,访问 deque 容器中的元素,比如
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int>d{ 1,2,3,4 };
cout << d[1] << endl;
d[1] = 5;
cout << d[1] << endl;
return 0;
}
使用此方法需确保下标 n 的值不会超过容器中存储元素的个数,否则会发生越界访问的错误。
- at() 成员函数
该函数会返回容器中指定位置处元素的引用形式,因此利用该函数的返回值,既可以访问指定位置处的元素,如果需要还可以对其进行修改。at() 成员函数会自行判定访问位置是否越界,如果越界则抛出std::out_of_range异常。例如:
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int>d{ 1,2,3,4 };
cout << d.at(1) << endl;
d.at(1) = 5;
cout << d.at(1) << endl;
return 0;
}
- front() 和 back()成员函数
front() 和 back(),它们分别返回 vector 容器中第一个和最后一个元素的引用,通过利用它们的返回值,可以访问(甚至修改)容器中的首尾元素。
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int> d{ 1,2,3,4,5 };
cout << "deque 首元素为:" << d.front() << endl;
cout << "deque 尾元素为:" << d.back() << endl;
d.front() = 10;
cout << "deque 新的首元素为:" << d.front() << endl;
d.back() = 20;
cout << "deque 新的尾元素为:" << d.back() << endl;
return 0;
}
- 遍历 deque 容器中指定区域元素的方法。例如:
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int> d{ 1,2,3,4,5 };
auto first = d.begin() + 1;
auto end = d.end() - 1;
while (first < end) {
cout << *first << " ";
++first;
}
return 0;
}
添加和删除元素
成员函数 | 功能 |
---|
push_back() | 在容器现有元素的尾部添加一个元素,和 emplace_back() 不同,该函数添加新元素的过程是,先构造元素,然后再将该元素移动或复制到容器的尾部。 | pop_back() | 移除容器尾部的一个元素。 | push_front() | 在容器现有元素的头部添加一个元素,和 emplace_back() 不同,该函数添加新元素的过程是,先构造元素,然后再将该元素移动或复制到容器的头部。 | pop_front() | 移除容器尾部的一个元素。 | emplace_back() | C++ 11 新添加的成员函数,其功能是在容器尾部生成一个元素。和 push_back() 不同,该函数直接在容器头部构造元素,省去了复制或移动元素的过程。 | emplace_front() | C++ 11 新添加的成员函数,其功能是在容器头部生成一个元素。和 push_front() 不同,该函数直接在容器头部构造元素,省去了复制或移动元素的过程。 | insert() | 在指定的位置直接生成一个元素。和 emplace() 不同的是,该函数添加新元素的过程是,先构造元素,然后再将该元素移动或复制到容器的指定位置。 | emplace() | C++ 11 新添加的成员函数,其功能是 insert() 相同,即在指定的位置直接生成一个元素。和 insert() 不同的是,emplace() 直接在容器指定位置构造元素,省去了复制或移动元素的过程。 | erase() | 移除一个元素或某一区域内的多个元素。 | clear() | 删除容器中所有的元素。 |
#include <deque>
#include <iostream>
using namespace std;
int main()
{
deque<int>d;
d.push_back(2);
d.pop_back();
d.push_front(2);
d.pop_front();
d.emplace_back(2);
d.emplace_front(3);
d.emplace(d.begin() + 1, 4);
for (auto i : d) {
cout << i << " ";
}
d.erase(d.begin());
d.erase(d.begin(), d.end());
return 0;
}
insert() 成员函数语法格式如下:
语法 | 功能 |
---|
iterator insert(pos,elem) | 在迭代器 pos 指定的位置之前插入一个新元素elem,并返回表示新插入元素位置的迭代器。 | iterator insert(pos,n,elem) | 在迭代器 pos 指定的位置之前插入 n 个元素 elem,并返回表示第一个新插入元素位置的迭代器。 | iterator insert(pos,first,last) | 在迭代器 pos 指定的位置之前,插入其他容器(不仅限于vector)中位于 [first,last) 区域的所有元素,并返回表示第一个新插入元素位置的迭代器。 | iterator insert(pos,initlist) | 在迭代器 pos 指定的位置之前,插入初始化列表(用大括号{}括起来的多个元素,中间有逗号隔开)中所有的元素,并返回表示第一个新插入元素位置的迭代器。 |
#include <iostream>
#include <deque>
#include <array>
using namespace std;
int main()
{
std::deque<int> d{ 1,2 };
d.insert(d.begin() + 1, 3);
d.insert(d.end(), 2, 5);
std::array<int, 3>test{ 7,8,9 };
d.insert(d.end(), test.begin(), test.end());
d.insert(d.end(), { 10,11 });
for (int i = 0; i < d.size(); i++) {
cout << d[i] << " ";
}
return 0;
}
list(链表容器)
是一个长度可变的、由 T 类型元素组成的序列,它以双向链表的形式组织元素,在这个序列的任何地方都可以高效地增加或删除元素(时间复杂度都为常数阶 O(1)),但访问容器中任意元素的速度要比前三种容器慢,这是因为 list 必须从第一个元素或最后一个元素开始访问,需要沿着链表移动,直到到达想要的元素。 list 容器中各个元素的前后顺序是靠指针来维系的,每个元素都配备了 2 个指针,分别指向它的前一个元素和后一个元素。其中第一个元素的前向指针总为 null,因为它前面没有元素;同样,尾部元素的后向指针也总为 null。
**优点:**它可以在序列已知的任何位置快速插入或删除元素(时间复杂度为O(1))。并且在 list 容器中移动元素,也比其它容器的效率高。
**缺点:**不能像 array 和 vector 那样,通过位置直接访问元素。举个例子,如果要访问 list 容器中的第 6 个元素,它不支持容器对象名[6]这种语法格式,正确的做法是从容器中第一个元素或最后一个元素开始遍历容器,直到找到该位置。
#include <list>
using namespace std;
初始化
- 创建一个没有任何元素的空 list 容器:
std::list<int> values;
和空 array 容器不同,空的 list 容器在创建之后仍可以添加元素,因此创建 list 容器的方式很常用。
- 创建一个包含 n 个元素的 list 容器:
std::list<int> values(10);
通过此方式创建 values 容器,其中包含 10 个元素,每个元素的值都为相应类型的默认值(int类型的默认值为 0)。
- 创建一个包含 n 个元素的 list 容器,并为每个元素指定初始值。例如:
std::list<int> values(10, 5);
创建了一个包含 10 个元素并且值都为 5 个 values 容器。
- 在已有 list 容器的情况下,通过拷贝该容器可以创建新的 list 容器。例如:
std::list<int> value1(10);
std::list<int> value2(value1);
采用此方式,必须保证新旧容器存储的元素类型一致。
- 通过拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 list 容器。例如:
int a[] = { 1,2,3,4,5 };
std::list<int> values(a, a+5);
std::array<int, 5>arr{ 11,12,13,14,15 };
std::list<int>values(arr.begin()+2, arr.end());
访问元素的几种方法
list 容器不支持随机访问,未提供下标操作符 [] 和 at() 成员函数,也没有提供 data() 成员函数。
- 通过 front() 和 back() 成员函数,可以分别获得 list 容器中第一个元素和最后一个元素的引用形式。举个例子:
#include <iostream>
#include <list>
using namespace std;
int main()
{
std::list<int> mylist{ 1,2,3,4 };
int &first = mylist.front();
int &last = mylist.back();
cout << first << " " << last << endl;
first = 10;
last = 20;
cout << mylist.front() << " " << mylist.back() << endl;
return 0;
}
- list 容器的迭代器访问 list 容存储的其他元素:
#include <iostream>
#include <list>
using namespace std;
int main()
{
const std::list<int> mylist{1,2,3,4,5};
auto it = mylist.begin();
cout << *it << " ";
++it;
while (it!=mylist.end())
{
cout << *it << " ";
++it;
}
return 0;
}
对于非 const 类型的 list 容器,迭代器不仅可以访问容器中的元素,也可以对指定元素的值进行修改。
添加(插入)元素方法
- push_front():向 list 容器首个元素前添加新元素;
- push_back():向 list 容器最后一个元素后添加新元素;
- emplace_front():在容器首个元素前直接生成新的元素;
- emplace_back():在容器最后一个元素后直接生成新的元素;
- emplace():在容器的指定位置直接生成新的元素;
- insert():在指定位置插入新元素;
- splice():将其他 list 容器存储的多个元素添加到当前 list 容器的指定位置处。
#include <iostream>
#include <list>
using namespace std;
int main()
{
std::list<int> values{1,2,3};
values.push_front(0);
values.push_back(4);
values.emplace_front(-1);
values.emplace_back(5);
values.emplace(values.end(), 6);
for (auto p = values.begin(); p != values.end(); ++p) {
cout << *p << " ";
}
return 0;
}
语法 | 说明 |
---|
iterator insert(pos,elem) | 在迭代器 pos 指定的位置之前插入一个新元素 elem,并返回表示新插入元素位置的迭代器。 | iterator insert(pos,n,elem) | 在迭代器 pos 指定的位置之前插入 n 个元素 elem,并返回表示第一个新插入元素位置的迭代器。 | iterator insert(pos,first,last) | 在迭代器 pos 指定的位置之前,插入其他容器(例如 array、vector、deque 等)中位于 [first,last) 区域的所有元素,并返回表示第一个新插入元素位置的迭代器。 | iterator insert(pos,initlist) | 在迭代器 pos 指定的位置之前,插入初始化列表(用大括号 { } 括起来的多个元素,中间有逗号隔开)中所有的元素,并返回表示第一个新插入元素位置的迭代器。 |
#include <iostream>
#include <list>
#include <array>
using namespace std;
int main()
{
std::list<int> values{ 1,2 };
values.insert(values.begin() , 3);
values.insert(values.end(), 2, 5);
std::array<int, 3>test{ 7,8,9 };
values.insert(values.end(), test.begin(), test.end());
values.insert(values.end(), { 10,11 });
for (auto p = values.begin(); p != values.end(); ++p)
{
cout << *p << " ";
}
return 0;
}
语法 | 说明 |
---|
void splice (iterator position, list& x); | position 为迭代器,用于指明插入位置;x 为另一个 list 容器。此格式的 splice() 方法的功能是,将 x 容器中存储的所有元素全部移动当前 list 容器中 position 指明的位置处。 | void splice (iterator position, list& x, iterator i); | position 为迭代器,用于指明插入位置;x 为另一个 list 容器;i 也是一个迭代器,用于指向 x 容器中某个元素。此格式的 splice() 方法的功能是将 x 容器中 i 指向的元素移动到当前容器中 position 指明的位置处。 | void splice (iterator position, list& x, iterator first, iterator last); | position 为迭代器,用于指明插入位置;x 为另一个 list 容器;first 和 last 都是迭代器,[fist,last) 用于指定 x 容器中的某个区域。此格式的 splice() 方法的功能是将 x 容器 [first, last) 范围内所有的元素移动到当前容器 position 指明的位置处。 |
splice() 成员方法移动元素的方式是,将存储该元素的节点从 list 容器底层的链表中摘除,然后再链接到当前 list 容器底层的链表中。这意味着,当使用 splice() 成员方法将 x 容器中的元素添加到当前容器的同时,该元素会从 x 容器中删除。
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> mylist1{ 1,2,3,4 }, mylist2{10,20,30};
list<int>::iterator it = ++mylist1.begin();
mylist1.splice(it, mylist2);
mylist2.splice(mylist2.begin(), mylist1, it);
mylist2.splice(mylist2.begin(), mylist1, mylist1.begin(), mylist1.end());
cout << "mylist1 包含 " << mylist1.size() << "个元素" << endl;
cout << "mylist2 包含 " << mylist2.size() << "个元素" << endl;
cout << "mylist2:";
for (auto iter = mylist2.begin(); iter != mylist2.end(); ++iter) {
cout << *iter << " ";
}
return 0;
}
程序执行结果为: mylist1 包含 0个元素 mylist2 包含 7个元素 mylist2:1 10 20 30 3 4 2
删除元素
成员函数 | 功能 |
---|
pop_front() | 删除位于 list 容器头部的一个元素。 | pop_back() | 删除位于 list 容器尾部的一个元素。 | erase() | 该成员函数既可以删除 list 容器中指定位置处的元素,也可以删除容器中某个区域内的多个元素。 | clear() | 删除 list 容器存储的所有元素。 | remove(val) | 删除容器中所有等于 val 的元素。 | unique() | 删除容器中相邻的重复元素,只保留一份。 | remove_if() | 删除容器中满足条件的元素。 |
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int>values{ 1,2,3,4 };
values.pop_front();
values.pop_back();
values.clear();
for (auto begin = values.begin(); begin != values.end(); ++begin)
{
cout << *begin << " ";
}
return 0;
}
实现删除 list 容器中 position 迭代器所指位置处的元素,例如:
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int>values{ 1,2,3,4,5 };
auto del = values.begin();
++del;
values.erase(del);
for (auto begin = values.begin(); begin != values.end(); ++begin)
{
cout << *begin << " ";
}
return 0;
}
实现删除 list 容器中 first 迭代器和 last 迭代器限定区域内的所有元素(包括 first 指向的元素,但不包括 last 指向的元素)。例如:
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int>values{ 1,2,3,4,5 };
auto first = values.begin();
++first;
auto last = values.end();
--last;
values.erase(first, last);
for (auto begin = values.begin(); begin != values.end(); ++begin)
{
cout << *begin << " ";
}
return 0;
}
通过调用无参的 unique(),仅能删除相邻重复(也就是相等)的元素,通过我们自定义去重的规则,可以更好的满足在不同场景下去重的需求。
#include <iostream>
#include <list>
using namespace std;
bool demo(double first, double second)
{
return (int(first) == int(second));
}
int main()
{
list<double> mylist{ 1,1.2,1.2,3,4,4.5,4.6 };
mylist.unique();
for (auto it = mylist.begin(); it != mylist.end(); ++it)
cout << *it << ' ';
cout << endl;
mylist.unique(demo);
for (auto it = mylist.begin(); it != mylist.end(); ++it)
std::cout << *it << ' ';
return 0;
}
通过将自定义的谓词函数(不限定参数个数)传给 remove_if() 成员函数,list 容器中能使谓词函数成立的元素都会被删除。举个例子:
#include <iostream>
#include <list>
using namespace std;
int main()
{
std::list<int> mylist{ 15, 36, 7, 17, 20, 39, 4, 1 };
mylist.remove_if([](int value) {return (value < 10); });
for (auto it = mylist.begin(); it != mylist.end(); ++it)
std::cout << ' ' << *it;
return 0;
}
forward_list(正向链表容器)
和 list 容器非常类似,只不过它以单链表的形式组织元素,它内部的元素只能从第一个元素开始访问,是一类比链表容器快、更节省内存的容器。 forward_list 容器具有和 list 容器相同的特性,即擅长在序列的任何位置进行插入元素或删除元素的操作,但对于访问存储的元素,没有其它容器(如 array、vector)的效率高。
由于单链表没有双向链表那样灵活,因此相比 list 容器,forward_list 容器的功能受到了很多限制。比如,由于单链表只能从前向后遍历,而不支持反向遍历,因此 forward_list 容器只提供前向迭代器,而不是双向迭代器。这意味着,forward_list 容器不具有 rbegin()、rend() 之类的成员函数。
forward_list 容器底层使用单链表,也不是一无是处。比如,存储相同个数的同类型元素,单链表耗用的内存空间更少,空间利用率更高,并且对于实现某些操作单链表的执行效率也更高。
通用的函数说明
函数 | 说明 |
---|
begin() | 返回指向容器中第一个元素的迭代器。 | end() | 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。 | rbegin() | 返回指向最后一个元素的迭代器。 | rend() | 返回指向第一个元素所在位置前一个位置的迭代器。 | cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 | cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 | crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 | crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 | assign() | 用新元素替换原有内容。 | operator=() | 复制同类型容器的元素,或者用初始化列表替换现有内容。 | size() | 返回实际元素个数。 | max_size() | 返回元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。 | capacity() | 返回当前容量。 | empty() | 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。 | resize() | 改变实际元素的个数。 | shrink _to_fit() | 将内存减少到等于当前元素实际所使用的大小。 | front() | 返回第一个元素的引用。 | back() | 返回最后一个元素的引用。 | operator | 使用索引访问元素。 | at() | 使用经过边界检査的索引访问元素。 | push_back() | 在序列的尾部添加一个元素。 | insert() | 在指定的位置插入一个或多个元素。 | emplace() | 在指定的位置直接生成一个元素。 | emplace_back() | 在序列尾部生成一个元素。 | pop_back() | 移出序列尾部的元素。 | erase() | 移出一个元素或一段元素。 | clear() | 移出所有的元素,容器大小变为 0。 | swap() | 交换两个容器的所有元素。 | data() | 返回指向容器中第一个元素的指针。 |
|