基本概念
STL(Standard Template Library,标准模版库)是惠普实验室开发的一系列软件的统称。现在主要出现在C++中,但在被引入C++之前该技术就已经存在了很长一段时间。
STL从广义上讲分为三类:
组件 | 描述 |
---|---|
<algorithm>(算法) | 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。 |
<container>(容器) | 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。 |
<iterator>(迭代器) | 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。 |
- 容器和算法通过迭代器可以进行无缝衔接。几乎所以代码都采用了模板类和模板函数的方1式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。在C++标准中,STL被组织为下面13个头文件:
<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack>、<utility>
1.序列式容器(Sequence containers)
- 每个元素都有固定位置——取决于插入时机和地点,与元素的值无关
- vector、deque、list、stack、queue
2.关联式容器(Associated containers)
- 元素位置取决于特定的排序准则,和插入顺序无关
- set、multiset、map、multimap
数据结构 | 描述 | 实现头文件 |
---|---|---|
向量(vector) | 连续存储的元素 | <vector> |
列表(list) | 由节点组成的双向链表,每个节点包含着一个元素 | <list> |
双队列(deque) | 连续存储的指向不同元素的指针所组成的数组 | <deque> |
集合(set) | 由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序 | <set> |
多重集合(multiset) | 允许存在两个次序相等的元素的集合 | <set> |
栈(stack) | 后进先出的值的排列 | <stack> |
队列(queue) | 先进先出的值的排列 | <queue> |
优先队列(priority queue) | 元素的次序是由作用于所储存的值对上的某种谓词决定的一种队列 | <queue> |
映射(map) | 又{键,值}对组成的集合,以某种作用于键对上的谓词排列 | <map> |
多重映射(multimap) | 允许键对有相同的次序映射 | <map> |
一、迭代器 <iterator>
1、迭代器的基本概念
- 什么是迭代器?
迭代器是一种检查容器内元素并且遍历容器内元素的数据类型 - 迭代器的作用:
迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围 - 为什么需要迭代器?
- STL提供每种容器的实现原理各不相同,如果没有迭代器,我们需要记住每一种容器中对象的访问方法,很显然这样会变得非常麻烦
- 每个容器都实现了一个迭代器用于对容器中对象的访问,虽然每个容器中的迭代器的实现方式不一样,但是对于用户来说,操作方式是一致的,也就是说:通过迭代器统一了对所有容器的访问方式。例如:无论哪个容器访问当前元素的下一个元素,我们都可以通过迭代器自增进行访问
- 迭代器是为了提高编程效率而开发的
2、迭代器失效
1.插入元素后造成迭代器失效
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int>::iterator it = v1.begin() + 3;
cout << *it << endl;
v1.insert(it, 8); //会改变容器的位置
cout << *it << endl; //迭代器指向的已不是容器的那个位置,系统会报错
for (it = v1.begin(); it != v1.end(); it++)
cout << *it << " ";
cout << endl;
return 0;
}
其输出结果为:
4
0
1 2 3 8 4
其中第二行的结果为0只是因为编译器的结果,因为在给v1容器插入8这个元素的时候,v1容器扩容了,会导致容器的位置发生整体改变,迭代器it所指向的位置就不是原来v1容器的位置了,it变成了一种野指针(使用野指针在VS编译器中会直接报错),也就是迭代器失效
解决方法
insert函数是有返回值的,其返回值是指向插入位置的迭代器
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int>::iterator it = v1.begin() + 3;
cout << *it << " ";
it = v1.insert(it, 8);
cout << *it << endl;
return 0;
}
此代码输出的结果为:
4 8
2.删除元素后造成迭代器失效
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int a[] = { 1,1,2,3,3,3,3,4,6,6 };
vector<int> v1(a, a + 10);
vector<int>::iterator it;
for (it = v1.begin(); it != v1.end(); it++)
if (*it == 3)
v1.erase(it);
for (it = v1.begin(); it != v1.end(); it++)
cout << *it << " ";
cout << endl;
return 0;
}
这段代码输出的结果为:
1 1 2 3 3 4 6 6
这时你可能会发现,这个v1容器中的“3”并没有删干净,而是保留了一半,这是为什么呢?
因为容器在删除元素时,会将该元素后面所有的元素整体往前移从而导致一个“3”被“跳过去”了
当然,解决这个问题并不难,只有在迭代器当前的位置不是“3”的时候才使it++,便可实现将“3”去除干净,即:
//将上述代码的删除“3”操作改为以下这样:
for (it = v1.begin(); it != v1.end(); it++)
while (*it == 3)
v1.erase(it);
结果将变为:
1 1 2 4 6 6
其实,erase( )函数也是有返回值的
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int a[] = { 1,1,2,3,3,3,3,4,6,6 };
vector<int> v1(a, a + 10);
vector<int>::iterator it;
for (it = v1.begin(); it != v1.end();)
if (*it == 3)
{
vector<int>::iterator it1;
it1 = v1.erase(it); //此时it这个迭代器会失效
cout << *it1 << " ";
}
else it++;
return 0;
}
其输出的结果为:
3 3 3 4
即:返回值是指向所删除位置的迭代器
所以,可以利用这个特性重新使得it指向下一个元素,即删除“3”的完整版代码:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int a[] = { 1,1,2,3,3,3,3,4,6,6 };
vector<int> v1(a, a + 10);
vector<int>::iterator it;
for (it = v1.begin(); it != v1.end();)
if (*it == 3)
it = v1.erase(it);
else it++;
for (it = v1.begin(); it != v1.end(); it++)
cout << *it << " ";
cout << endl;
return 0;
}
代码输出的结果为:
1 1 2 4 6 6
3.erase和insert的返回值都是迭代器
erase会返回删除元素的下一个元素的迭代器 当erase(迭代器it)后 it能再使用(被删除了) 因此在使用erase是要记录返回迭代器的位置 it = erase(迭代器it)
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int>v;
for (int i = 0; i < 10; i++)
{
v.push_back(i + 1);
}
auto it = v.begin();
it = v.erase(it); //如果v.erase(it)后*it报错
cout << *it << endl;
return 0;
}
二、容器 <container>
1、vector容器(向量)
1.简介
- vector是将元素置于一个动态数组中加以管理的容器
- vector可以随机存取元素(支持索引值直接存取,用[]操作符或at()的方法)
- vector尾部添加或移除元素非常快速,但是中部或头部插入或移除元素比较费时
2.vector对象的默认构造
vector采用模板类实现,vector对象的默认构造形式
vector<T> vecT;
vector<int> vecInt; //一个存放int类型元素的vector容器
vector<float>vecFloat; //一个存放float类型元素的vector容器
vector<string>vecString; //一个存放string类型元素的vector容器
class CA{};
vector<CA*> vecpCA; //用于存放类CA的指针的vector容器
vector<CA> vecCA; //用于存放类CA的vector容器
//所以此时CA必须提供CA的拷贝构造函数,以保证CA对象之间拷贝正常
3.vector对象的带参数构造
- vec(beg, end); //构造函数将 [beg, end) 区间中的元素拷贝给本身。注意该区间是左闭右开区间!
- vec(n, elem); //构造函数将n个elem拷贝给本身
- vec(const vector &vec); //拷贝构造函数
接下来是上述理论的实例:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int arr[] = { 1,2,3,4,5 };
vector<int> v1(arr, arr + 5); //构造形如vector(beg, end);的容器
vector<int> v2(3, 10); //构造形如vector(n, elem);的容器
for (int i = 0; i < 3; i++)
cout << v2[i] << " ";
cout << endl;
vector<int> v3(v1); //构造形如vector(const vector &vec);的容器
for (int i = 0; i < 5; i++)
cout << v3[i] << " ";
return 0;
}
输出结果是这样的:
10 10 10
1 2 3 4 5
4.vector的赋值
- vec.assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开区间!
- vec.assign(n, elem); //将n个elem拷贝赋值给本身
注意:在使用assign函数时,会先将容器内的原内容清空,再赋值。尽管原内容可能比要赋值的内容多
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1(8, 10);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
int arr[] = { 1,2,3,4,5 };
v1.assign(arr, arr + 5);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
return 0;
}
以上代码输出的结果是:
10 10 10 10 10 10 10 10
1 2 3 4 5
- vector中的两个迭代器(指针)——begin()和end()
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1, v2, v3, v4;
int arr[] = { 1,2,3,4,5 };
v1.assign(arr, arr + 5);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
//用其他容器的迭代器作为参数
//begin()指向的是容器中第一个元素
//end()指向的是容器中最后一个元素的下一个元素
v2.assign(v1.begin(),v1.end());
for (int i = 0; i < v2.size(); i++)
cout << v2[i] << " ";
cout << endl;
//如果想实现只将第3个数和第4个数传过去
v3.assign(v1.begin() + 2,v1.end() - 1);
for (int i = 0; i < v3.size(); i++)
cout << v3[i] << " ";
cout << endl;
return 0;
}
以上代码输出的结果为:
1 2 3 4 5
1 2 3 4 5
3 4
- vec1& operator=(const vector &vec2); //重载等号操作符
- vec1.swap(vec2); //将vec与本身的元素互换
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int arr[] = { 2,7,8,5,1 };
vector<int> v1(arr, arr + 5);
vector<int> v2, v3;
v2 = v1; //使用重载等号操作符给v2赋值
v3.assign(5, 8);
//分别打印v2,v3容器内的元素
cout << "交换之前,v2与v3的值" << endl;
for (int i = 0; i < v2.size(); i++)
cout << v2[i] << " ";
cout << endl;
for (int i = 0; i < v3.size(); i++)
cout << v3[i] << " ";
cout << endl << endl;
//交换v2与v3的元素
v2.swap(v3); //写成v3.swap(v2);也是一样的
//再次打印v2,v3容器内的元素
cout << "交换之后,v2与v3的值" << endl;
for (int i = 0; i < v2.size(); i++)
cout << v2[i] << " ";
cout << endl;
for (int i = 0; i < v3.size(); i++)
cout << v3[i] << " ";
cout << endl;
return 0;
}
5.vector的大小(长度/个数)
- vec.size(); //返回vector容器中元素的个数
- vec.empty(); //判断vector容器是否为空
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
cout << "v1.size:" << v1.size() << endl;
//判断v1是否为空
if (v1.empty())
cout << "v1 is empty" << endl;
else cout << "v1 is not empty" << endl;
int arr[] = { 1,2,3,4,5 };
v1.assign(arr, arr + 5);
//给v1赋值之后再次判断v1是否为空
cout << "v1.size:" << v1.size() << endl;
if (v1.empty())
cout << "v1 is not empty" << endl;
else cout << "v1 is not empty" << endl;
return 0;
}
以上代码的结果是:
v1.size:0
v1 is empty
v1.size:5
v1 is not empty
- vec.resize(num); //重新指定容器的长度为num。若容器变长,则以默认值重新填充新位置;若容器变短,则末尾超出容器长度的元素被删除。
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
int arr[] = { 1,2,3,4,5 };
v1.assign(arr, arr + 5);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
//将容器的长度增长
v1.resize(10);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
//将容器的长度缩短
v1.resize(3);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
return 0;
}
而以上代码运行的结果为:
1 2 3 4 5
1 2 3 4 5 0 0 0 0 0
1 2 3
也能由此看出在这个编译器(VS)中,resize()函数的默认值为0,但更多时候需要将扩展的元素赋为特定的值
- vec.resize(num, elem); //重新指定容器的长度为num。若容器变长,则以elem值重新填充新位置;若容器变短,则末尾超出容器长度的元素被删除。
//只需要将上述resize()函数的num后加上想要的值即可
//若将上述代码的增长部分改为以下代码
v1.resize(10,100);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
则代码的运行结果是这样的:
1 2 3 4 5
1 2 3 4 5 100 100 100 100 100
1 2 3
6.vector元素的访问方式(数据存取)
vector容器相当于动态数组,长度也是动态的
- vec[idx]; //返回下标(索引)idx所指的数据,越界时,运行直接报错
- vec.at(idx); //返回下标(索引)idx所指的数据,如果idx越界,抛出异常终止的原因(程序仍然会异常终止)
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
int arr[] = { 1,2,3,4,5 };
v1.assign(arr, arr + 5);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
//使用下标访问vector容器中的元素
//如果下标越界,可能会导致程序异常终止
v1[8]=100; //使用该语句将不知道出错在哪
v1.at(8) = 100; //使用该语句将弹出出错的原因
return 0;
}
7.在vector末尾的添加移除操作
- vec.push_back(num); //在vec的末尾插入一个num元素
- vec.pop_back(); //删除vec的末尾的一个元素
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
int arr[] = { 1,2,3,4,5 };
v1.assign(arr, arr + 5);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
v1.pop_back();
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
v1.push_back(10);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
return 0;
}
以上代码输出的结果是:
1 2 3 4 5
1 2 3 4
1 2 3 4 10
8.vector的插入
- vec.insert(pos, elem); //在pos位置插入一个elem元素,返回新数据的位置
- vec.insert(pos, n, elem); //在pos位置插入n个elem元素,无返回值
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
int arr[] = { 1,2,3,4,5 };
v1.assign(arr, arr + 5);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
//在第3个元素后填入100,之后的元素往后移一个位置
v1.insert(v1.begin() + 3, 100);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
//在第2个元素后填入n个9,之后的元素往后移n个位置
v1.insert(v1.begin() + 2, 3, 9);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
return 0;
}
上述代码的结果为:
1 2 3 4 5
1 2 3 100 4 5
1 2 9 9 9 3 100 4 5
- vec.insert(pos, beg, end); //在pos位置插入[beg, end)区间的数据,无返回值
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
int arr[] = { 1,1,1,1,1,1 };
v1.assign(arr, arr + 5);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
int b[] = { 2,3,4,5,6,7 };
//在第2个元素后插入[beg, end)区间,之后的元素往后移n个位置
v1.insert(v1.begin() + 2, b + 1, b + 4);
for (int i = 0; i < v1.size(); i++)
cout << v1[i] << " ";
cout << endl;
return 0;
}
上述代码输出的结果为:
1 1 1 1 1
1 1 3 4 5 1 1 1
9.vector容器的iterator(迭代器)类型
- vector<int>::iterator iter; //迭代器变量名为iter
- vector容器的迭代器属于“随机访问迭代器”:迭代器一次可以移动多个位置
成员函数 | 功能 |
---|---|
begin( ) | 返回指向容器中第一个元素的正向迭代器;如果是const类型容器,则该函数返回的是常量正向迭代器 |
end( ) | 返回指向容器最后一个元素的后一个位置的正向迭代器;如果是const类型容器,则该函数返回的是常量正向迭代器。此函数通常和begin( )搭配使用 |
rbegin( ) | 返回指向最后一个元素的反向迭代器;如果是const类型容器,则该函数返回的是常量反向迭代器 |
rend( ) | 返回指向容器最后一个元素的前一个位置的反向迭代器;如果是const类型容器,则该函数返回的是常量反向迭代器。此函数通常和rbegin( )搭配使用 |
cbegin( ) | 和begin( )功能类似,只不过其返回的迭代器类型为常量正向迭代器,不能用于修改元素 |
cend( ) | 和end( )功能类似,只不过其返回的迭代器类型为常量正向迭代器,不能用于修改元素 |
crbegin( ) | 和rbegin( )功能类似,只不过其返回的迭代器类型为常量正向迭代器,不能用于修改元素 |
crend( ) | 和rend( )功能类似,只不过其返回的迭代器类型为常量正向迭代器,不能用于修改元素 |
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1;
int iarray[] = { 100,1,20,30,40 };
v1.assign(iarray, iarray + 5);
//构造一个迭代器对象it
vector<int>::iterator it;
// 让迭代器it指向v1容器的第一个元素
it = v1.begin();
//输出此时的it指向的元素
cout << *it << endl;
//通过循环使用迭代器遍历v1容器中的所有元素
for (it = v1.begin(); it != v1.end(); it++)
cout << *it << " ";
cout << endl;
it = v1.begin();
it = it + 2;
cout << *it;
return 0;
}
此时的输出结果为:
100
100 1 20 30 40
20
2、deque容器(双端队列)
1.deque容器简介
- deque是“double-ended queue"的缩写,和vector一样都是STL的容器
- deque是双端数组而vector是单端的
- deque在接口上和vector非常相似,在许多操作的地方可以直接替换
- deque可以随机存取元素(支持索引值直接存取,用[ ]操作符或at( )方法)
- deque头部和尾部添加或移除元素都非常快速。但是在中部安插元素或移除元素比较费时
- 需要使用头文件#include<deque>
2.deque容器的操作
deque与vector在操作上几乎一样,deque多两个函数
#include<deque>
deque.push_front(elem); //在容器头部插入一个数据
deque.pop_front(); //删除容器的第一个数据
以下是在头部插入数据的实例:
#include<iostream>
#include<deque>
using namespace std;
int main()
{
deque<int> d1 = { 10,20,30,40 };
//在头部插入一个元素
d.push_front(100);
deque<int>::iterator it;
for (it = d1.begin(); it != d1.end(); it++)
cout << *it << " ";
cout << endl;
return 0;
}
以上代码输出的结果为:
100 10 20 30 40
3、stack容器(栈)
1.stack对象的默认构造
- stack采用模板类实现,stack对象的默认构造形式为:stack<T> s;
- stack<int> s; //一个存放int的stack容器
- stack<float> s; //一个存放float的stack容器
- stack<string> s; //一个存放string的stack容器
2.stack常用的成员函数
- stack.push(elem) //在栈顶增加元素(入栈)
- stack.pop( ) //移除栈顶元素(出栈)
- stack.empty( ) //堆栈为空则返回真
- stack.size( ) //返回栈中元素数目
- stack.top( ) //返回栈顶元素
#include<iostream>
#include<stack>
using namespace std;
int main()
{
//以默认构造方式构造一个stack容器
stack<int> s;
//入栈
for(int i=1;i<8;i+=2)
s.push(i);
//stack容器没有迭代器,因为栈是不允许遍历的
cout<<s.top()<<" "; //top函数返回栈顶元素,不会删除栈顶元素
s.pop(); //出栈,删除栈顶元素
cout<<s.top();
return 0;
}
以上代码输出的结果为:
7 5
栈空之后就不能继续移除栈顶元素了,我们通常会选择通过判断栈是否为空来作为是否进行的条件,当然,在栈空之后再使用pop( )函数是很危险的
#include<iostream>
#include<stack>
using namespace std;
int main()
{
stack<int> s;
for (int i = 1; i < 10; i += 2)
s.push(i);
//可以通过while来显示并清除栈内所有元素
//此时,由于栈是“先入后出”的模式,所以会以与入栈时相反的顺序出栈
while(!s.empty()) //或者写成s.empty()==flase的形式
{
cout<<s.top()<<" ";
s.pop();
}
return 0;
}
以上代码输出的结果为:
9 7 5 3 1
3.stack对象的拷贝构造与赋值
- stack(const stack &stk); //拷贝构造函数
- stack& operator = (const stack &stk); //重载等号操作符
#include<iostream>
#include<stack>
using namespace std;
int main()
{
stack<int> stk1;
for (int i = 1; i < 10; i += 2)
stk1.push(i);
stack<int> stk2(stk1); //将stk1拷贝到skt2中
while (!stk2.empty())
{
cout << stk2.top() << " ";
stk2.pop();
}
return 0;
}
以上代码输出的结果为:
9 7 5 3 1
很显然,是和stk1的结果是一样的。但对于“=”,在定义时使用和在语句中使用的意义是不一样的
//接上一条代码
stack<int> stk3 = stk; //调用拷贝构造函数,与上述stk2是等价的
stk3 = stk1; //此时调用了“=”的重载功能
4.stack容器的大小(栈内元素的个数)
#include<iostream>
#include<stack>
using namespace std;
int main()
{
stack<int> stk1;
for (int i = 1; i < 10; i += 2)
stk1.push(i);
stack<int> stk2(stk1); //将stk1拷贝到skt2中
while (!stk2.empty())
{
cout << stk2.top() << " ";
stk2.pop();
}
cout << endl;
cout << "stk1 size:" << stk1.size() << endl;
return 0;
}
上述代码输出的结果为:
9 7 5 3 1
stk1 size:5
stack容器并没有提供resize( )方法
4、queue容器(队列)
- queue是队列容器,是一种“先进先出”的容器
- 需要使用头文件#include<queue>
1.queue对象的默认构造
- queue采用模板类,queue对象的默认构造形式:queue<T> q;
#include<queue>
#include<cstring>
queue<int> queInt; //一个存放int的queue容器
queue<float> queFloat; //一个存放float的queue容器
queue<string> queString; //一个存放string的queue容器
2.queue容器的常用成员函数
- queue.push(elem) //在队列尾部增加元素(入队)
- queue.pop( ) //删除队首元素(出队)
- queue.empty( ) //队列为空则返回真
- queue.size( ) //返回队列中元素数目
- queue.front( ) //返回队首元素(可以作为左值被修改)
- queue.back( ) //返回队尾元素
#include<iostream>
#include<queue>
using namespace std;
int main()
{
//默认构造方式构造一个队列容器
queue<int> q1;
//向队列中添加元素
for (int i = 1; i < 10; i += 2)
q1.push(i);
//queue容器也没有提供迭代器,因为不能够遍历
while(!q1.empty())
{
cout << q1.front() << endl;
q1.pop(); //删除第一个元素(队首元素)
}
return 0;
}
3.queu容器对象的拷贝构造与赋值
- queue(const queue &que); //拷贝构造函数
- queue& operator = (const queue &que); //重载等号操作符
基本上的与stack容器相同,不再赘述了
5、list容器(双向链表)
慎用STL的list,空间复杂度和时间复杂度都容易超出限制
1.基本概念
- list是一个双向链表容器,可高效地进行插入和删除元素
- list不可以随机存取元素,所以不支持at.(pos)函数与[]操作符
- it++ //迭代器自增、自减是正确的
- it+5 //迭代器一次移动多个是错误的
- 需要使用#include<list>头文件
2.list容器的头部和尾部操作
- list采用模板类实现,对象的默认构造形式:list<T> lst,例如:
list<int> lstInt; //定义一个存放int的list容器
list<float> lstFloat; //定义一个存放float的list容器
list<string> lstString; //定义一个存放string的list容器
list头尾添加移除操作
- list.push_back(elem); //在容器尾部加入一个元素
- list.pop_back( ); //删除容器中最后一个元素
- list.push_front(elem); //在容器头部加入一个元素
- list.pop_front( ); //删除容器中第一个元素
#include<iostream>
#include<list>
using namespace std;
int main()
{
list<int> lst;
lst.push_back(10); //在尾部插入一个元素(类似尾插法)
lst.push_front(20); //在头部插入一个元素(类似头插法)
//构造一个迭代器
list<int>::iterator it;
for (it = lst.begin(); it != lst.end(); it++)
cout << *it << " ";
cout << endl;
//删除最后一个元素
lst.pop_back();
for (it = lst.begin(); it != lst.end(); it++)
cout << *it << " ";
cout << endl;
lst.push_back(10); //把刚才删除的元素加回来
//删除第一个元素
lst.pop_front();
for (it = lst.begin(); it != lst.end(); it++)
cout << *it << " ";
cout << endl;
lst.push_front(20); //把刚才删除的元素加回来
//返回第一个节点
int x = lst.front();
cout << "front:" << x << endl;
//返回最后一个节点
x = lst.back();
cout << "back:" << x << endl;
//list容器是可遍历,内部数据同样是可改变的
lst.front() = 100; //将第一个节点赋值为100
lst.back() = 200; //将最后一个节点赋值为200
for (it = lst.begin(); it != lst.end(); it++)
cout << *it << " ";
cout << endl;
return 0;
}
以上代码输出的结果为:
20 10
20
10
front:20
back:10
100 200
3.list的反向迭代器
- list容器的迭代器是“双向迭代器”:双向迭代器从两个方向读写容器。除了提供前向迭代器的全部操作之外,双向迭代器还提供前置和后置的自增、自减运算
list.begin(); //返回容器中第一个元素的迭代器
list.end(); //返回容器中最后一个元素之后的迭代器
list.rbegin(); //返回容器中倒数第一个元素的迭代器
list.rend(); //返回容器中倒数最后一个元素的后面的迭代器
注意,不论正向迭代器还是反向迭代器,it++的方向都是从begin( )到end( )
#include<iostream>
#include<list>
using namespace std;
int main()
{
list<int> lst;
lst.push_back(10);
lst.push_front(20);
lst.push_back(30);
lst.push_back(40);
//20 10 30 40
//list容器的反向迭代器
list<int>::reverse_iterator it1;
for (it1 = lst.rbegin(); it1 != lst.rend(); it1++)
cout << *it1 << " ";
cout << endl;
return 0;
}
以上代码输出的结果为:
40 30 10 20
同时证明了list是一个双向链表
4. list对象的带参数构造
- list(n,elem); //构造函数将n个elem拷贝给本身
- list(beg,end); //构造函数将[beg,end)区间中的元素拷贝给本身
- list(const list &lst); //拷贝构造函数
#include<iostream>
#include<list>
using namespace std;
int main()
{
list<int> lst1(3, 5); //将
list<int>::iterator it;
for (it = lst1.begin(); it != lst1.end(); it++)
cout << *it << " ";
cout << endl;
list<int> lst;
lst.push_back(10);
lst.push_front(20);
lst.push_back(30);
lst.push_back(40);
//20 10 30 40
//list不支持+n的情况存在
//list<int> lst2(lst.begin(),lst.begin()+2);是错误的
list<int>::iterator beg = lst.begin();
list<int>::iterator end = lst.begin();
end++; end++;
for (it = beg; it != end; it++)
cout << *it << " ";
cout << endl;
list<int> lst3(lst); //拷贝构造函数
for (it = lst3.begin(); it != lst3.end(); it++)
cout << *it << " ";
cout << endl;
return 0;
}
以上输出的结果为:
5 5 5
20 10
20 10 30 40
5.list容器的赋值
- list.assign(beg,end); //将[beg,end)区间中的数据拷贝赋值给本身
- list.assign(n,elem); //将n个elem拷贝赋值给本身
- list& operator = (const list &lst); //重载等号操作符
- list.swap(lst); //交换两个头指针的位置,类似于交换元素
#include<iostream>
#include<list>
using namespace std;
int main()
{
list<int> lst;
lst.push_back(10);
lst.push_front(20);
lst.push_back(30);
lst.push_back(40);
//20 10 30 40
list<int> lst1;
lst1.assign(lst.begin(), lst.end());
list<int>::iterator it;
for (it = lst1.begin(); it != lst1.end(); it++)
cout << *it << " ";
cout<<endl;
lst1.assign(3,6); //覆盖式赋值(会清空赋值之前的元素),而不是追加
for (it = lst1.begin(); it != lst1.end(); it++)
cout << *it << " ";
list<int> lst2(4,8);
lst2.swap(lst1); //交换两个头指针的位置
for (it = lst1.begin(); it != lst1.end(); it++)
cout << *it << " ";
cout<<endl;
for (it = lst2.begin(); it != lst2.end(); it++)
cout << *it << " ";
cout<<endl;
return 0;
}
以上代码输出的结果为:
20 10 30 40
6 6 6
8 8 8 8
6 6 6
6.list的成员函数
(1)专用成员函数
- merge(b):将链表b与调用链表合并,在合并之前,两个链表必须已经排序,合并后经过排序的链表被保存在调用链表中,b为空。
- remove(val):从链表中删除val的所有节点。
- splice(pos,b):将链表b的内容插入pos的前面,b为空。
- reverse( ):将链表翻转。
- sort( ):将链表排序。
- unique( ):将连续的相同元素压缩为单个元素。不连续的相同元素无法压缩,因此一般先排序后去重。
(2)其他成员函数
- push_front(x)/push_back(x):x从链表头或尾入。
- pop_front( )/pop_back( ):从链表头或尾出。
- front( )/back( ):返回链表头或尾元素。
- insert(p, t):在p之前插入t。
- erase§:删除p。
- clear( ):清空链表。
6、set/multiset容器(集合)
1.set/multiset容器的基本概念
- set是一个集合容器,其中所包含的元素是唯一的,集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置
- set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树。在插入操作和删除操作上比vector快
- set不可以直接存取元素(不可以使用at.(pos)与[ ]操作符)
- multiset与set的区别:set支持唯一键值,每个元素值只能出现一次;而multiset中同一值可以出现多次
- 不可以直接修改set或multiset容器中的元素值,因为该类容器是自动排序的。如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素
- set或multiset的迭代器为双向访问,不支持随机访问。执行一次“++”和“- -”操作的时间复杂度均为O(logn)。默认的元素顺序为升序,也可以通过第2个模版的参数设置为降序
- 头文件为#includ<set>
2.set容器的插入和迭代器
- set.insert(elem); //在容器中插入元素
- set.begin( ); //返回容器中第一个数据的迭代器
- set.end( ); //返回容器中最后一个数据之后的迭代器
- set.rbegin( ); //返回容器中倒数第一个元素的迭代器
- set.rend( ); //返回容器中倒数最后一个元素的后面的迭代器
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int> s1;
s1.insert(3);
s1.insert(1);
s1.insert(5);
s1.insert(2);
set<int>::iterator it;
for (it = s1.begin(); it != s1.end(); it++)
cout << *it << ' ';
cout << endl;
return 0;
}
上述代码输出的结果为:
1 2 3 5
由此可知,set容器的默认排序是升序排列的
如果想让set按照降序的方式进行排序,则可以按照以下方式达成:
//将上述的 set<int> s1; 改为 set<int,greater<int> > s1;
//注意greater<int>后面有空格,避免两个符号一起>>,有右移歧义
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int,greater<int> > s1;
s1.insert(3);
s1.insert(1);
s1.insert(5);
s1.insert(2);
set<int>::iterator it;
for (it = s1.begin(); it != s1.end(); it++)
cout << *it << ' ';
cout << endl;
return 0;
}
上述代码输出的结果则为:
5 3 2 1
3.set的成员函数
- size/empty/clear:元素个数、判空、清空
- begin/end:开始位置和结束位置
- insert(x):将元素x插入集合
- erase(x):删除所有等于x的元素
- erase(it):删除it迭代器指向的元素
- find(x):查找元素x在集合中的位置,若不存在,则返回end
- count(x):统计等于x的元素个数
- lower_bound(x)/upper_bound(x):返回大于或等于x的最小元素位置、大于x的最小元素位置
7、map/multiset容器(映射)
1.map/multiset的基本概念
map的键和值可以是不同的类型,键是唯一的,每个键都对应一个值。multimap与map类似,只是允许一个键对应多个值。map可被当作哈希表使用,它建立了从键(关键字)到值的映射。map是键和值的一一映射,multimap是一对多映射。使用map或multimap时需要引入头文件#include<map>
map的迭代器和set类似,支持双向访问,不支持随机访问,执行一次“++”和“- -”操作的时间复杂度O(logn)。默认的元素顺序为升序,也可以通过第3个模板参数设置为降序。
map<string, int> a; //升序
map<string, int, greater<string> >a; //降序
上述map模板的第1个参数为键的类型,第2个参数为值的类型,第3个参数可选,用于对键进行排序的比较函数或对象。
在map中,键和值是一对数,可以使用make_pair生成一对数(键,值)进行插入。
a.insert(mark_pair(s,i));
输出时,可以分别输出第1个元素(键)和第2个元素(值)。
map<string, int>::iterator it;
for (it = a.begin(); it != a.end(); it++)
cout << it->first << "\t" << it->second << endl;
2.map的成员函数
- size/empty/clear:元素个数、判空、清空
- begin/end:开始位置和结束位置
- insert(x):将元素x插入集合(x为二元组)
- erase(x):删除所有等于x的元素(x为二元组)
- erase(it):删除it指向的元素(it为指向二元组的迭代器)
- find(k):查找键位k的二元组的位置,若不存在,则返回尾指针
可以通过“[ ]“操作符直接得到键映射的值,也可以通过赋值操作改变键映射的值,例如h[key]=val。是不是特别像哈希表?
例如,可以用map统计字符串出现的次数。
map<string, int> mp;
string word;
for (int i = 0; i < n; i++)
{
cin >> s;
mp[s]++;
}
cout << "输入字符串s,查询该字符串出现的次数:" << endl;
cin >> s;
cout << mp[s] << endl;
需要特别注意的是,如果查找的key不存在,则执行h[key]之后会自动新建一个二元组(key, 0)并返回0,进行多次查找之后有可能包含很多无用的二元组。因此使用查找时最好先查询key是否存在。
if (mp.find(s) != mp.end())
cout << mp[s] << endl;
else
cout << "没找到!" << endl;
3.multimap的基本阐述
multimap和map类似,不同的是一个键可以对应多个值。由于是一对多的映射关系,multimap不能使用“[ ]”操作符。
例如,可以添加多个关于X国的数据:
mutimap<string, int> mp;
string s1("X"), s2("Y");
mpinsert(make_pair(s1, 50));
mpinsert(make_pair(s1, 55));
mpinsert(make_pair(s1, 60));
mpinsert(make_pair(s2, 30));
mpinsert(make_pair(s2, 20));
mpinsert(make_pair(s1, 10));
输出所有关于X国的数据:
mutimap<string, int>::iterator it;
it = mp.find(s1);
for (int k = 0; k < mp.count(s1); k++, it++)
cout << it->first << "--" << it->second << endl;
由于前段时间的蓝桥杯备赛,导致发布时间有点晚,本期的内容就到此为止了,如果有什么问题欢迎大家在评论区交流讨论。