C++的STL中,有哪几种容器?
容器
在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器。
序列容器(7个)
vector
vector是一段连续的内存地址,基于数组实现,其提供了自动内存管理功能(采用了STL普遍的内存管理器allocator),可以动态改变对象长度,提供随机访问。在尾部添加和删除元素的时间是常数的,但在头部或中间就是线性时间。
在vector容器中有以下几个关于大小的函数:
- size():返回容器的大小
- max_size():返回容器扩展极限的最大存储的元素数量
- empty():判断容器是否为空
- capacity():返回容器当前能够容纳的元素数量
vector内存扩展方式:
- 另觅更大空间;
- 将原数据复制过去;
- 释放原空间。
一般需要注意vector的内存分配问题,原因是:
- 容器的大小一旦超过capacity的大小,vector会重新配置内部的存储器,导致和vector元素相关的所有reference、pointers、iterator都会失效。
- 内存的重新配置会很耗时间,频繁的realloc操作将极大降低程序效率。
- vector的capacity指当前状态下的最大容量。这个值如果不指定,则开始是0,加入第一个值时,由于capacity不够,向操作系统申请了长度为1的内存。在这之后每次capacity不足时,都会重新向操作系统申请长度为原来两倍的内存。
避免内存重新配置的方法:
- Reserve()保留适当容量
在创建容器后,第一时间为容器分配足够大的空间,避免重新分配内存。
std :: vector<int> v; //create an empty vector
v.reverse(80); //reserve memory for 80 elements
- 利用构造函数创建出足够空间
该方法是创建容器时,利用构造函数初始化的出足够的空间,
std::vector<int> v(80);
//1.定义和初始化
vector<int> vec1; //默认初始化,vec1为空
vector<int> vec2(vec1); //使用vec1初始化vec2
vector<int> vec3(vec1.begin(),vec1.end()); //使用vec1初始化vec2
vector<int> vec4(10); //10个值为0的元素
vector<int> vec5(10,4); //10个值为4的元素
//2.常用操作方法
vec1.push_back(100); //尾部添加元素
int size = vec1.size(); //元素个数
bool isEmpty = vec1.empty(); //判断是否为空
cout<<vec1[0]<<endl; //取得第一个元素
vec1.insert(vec1.end(), 5, 3); //从vec1.back位置插入5个值为3的元素
vec1.pop_back(); //删除末尾元素
vec1.erase(vec1.begin(), vec1.begin() + 2); //删除vec1[0]-vec1[2]之间的元素,不包括vec1[2],其他元素前移
cout<<(vec1 == vec2) ? true : false; //判断是否相等==、!=、>=、<=
vector<int> :: iterator iter = vec1.begin(); //获取迭代器首地址
vector<int> :: const_iterator c_iter = vec1.begin(); //获取const类型迭代器
vec1.clear(); //清空元素
//3.遍历
//下标法
int length = vec1.size();
for(int i=0; i<length; i++)
{
cout<<vec1[i];
}
cout<<endl<<endl;
//迭代器法
vector<int> :: iterator iter = vec1.begin();
for( ; iter != vec1.end(); iter++)
{
cout<<*iter;
}
list
list是非连续的内存,基于链表实现,属于循环双向链表,目的是实现快速插入和删除,但是随即访问却是比较慢。
//1.定义和初始化
list<int> lst1; //创建空list
list<int> lst2(3); //创建含有三个元素的list
list<int> lst3(3,2); //创建含有三个元素为2的list
list<int> lst4(lst2); //使用lst2初始化lst4
list<int> lst5(lst2.begin(), lst2.end()); //同lst4
//2.常用操作方法
lst1.assign(lst2.begin(),lst2.end()); //分配3个值为0的元素
lst1.push_back(10); //末尾添加值
lst1.pop_back(); //删除末尾值
lst1.begin(); //返回首值的迭代器
lst1.end(); //返回尾值的迭代器
lst1.clear(); //清空值
bool isEmpty1 = lst1.empty(); //判断为空
lst1.erase(lst1.begin(),lst1.end()); //删除元素
lst1.front(); //返回第一个元素的引用
lst1.back(); //返回最后一个元素的引用
lst1.insert(lst1.begin(), 3, 2); //从指定位置插入个3个值为2的元素
lst1.rbegin(); //返回第一个元素的前向指针
lst1.remove(2); //相同的元素全部删除
lst1.reverse(); //反转
lst1.size(); //含有元素个数
lst1.sort(); //排序
lst1.unique(); //删除相邻重复元素
//3.遍历
//迭代器法
for(list<int> :: const_iterator iter = lst1.begin(); iter != lst1.end(); iter++)
{
cout<<*iter;
}
deque
双端队列(double-ended queue),支持随机访问,与vector类似,主要区别在于,从deque对象的开始位置插入和删除元素的时间也是常数的,所以若多数操作发生在序列的起始和结尾处,则应考虑使用deque。为实现在deque两端执行插入和删除操作的时间为常数时间这一目的,deque对象的设计比vector更为复杂,因此,尽管二者都提供对元素的随机访问和在序列中部执行线性时间的插入和删除操作,但vector容器执行这些操作时速度更快些。
#include <deque>
#include <iostream>
#include <algorithm>
#include <stdexcept>
using namespace std;
int main()
{
//1. 初始化
deque<int> v;
deque<int> :: iterator iv;
deque<int> v1{ 1, 2, 3, 4, 5};
deque<int> v2(v1);
deque<int> v3(v1.begin(), v1.end());
//v.assign(10, 2); //将10个值为2的元素赋到deque中
v.assign(v1.begin(), v1.end()); //接受序列容器的范围
cout << v.size() << endl; //返回deque实际含有的元素数量
//2. 添加
v.push_front(666);
for (int i = 0; i < 10; i++)
v.push_back(i);
for_each(v.begin(), v.end(), print); //需要#include <algorithm>, print为自定义输出函数
cout << v.size() << endl;
//3. 插入及遍历、逆遍历
v.insert(v.begin() + 3, 99);
v.insert(v.end() - 3, 99);
for_each(v.begin(), v.end(), print);
for_each(v.rbegin(), v.rend(), print); //在逆序迭代器上做++运算将指向容器中的前一个元素
//一般遍历写法
for(iv = v.begin(); iv != v.end(); ++iv)
cout << *iv << " ";
//4. 删除
v.erase(v.begin() + 3);
for_each(v.begin(), v.end(), print);
v.insert(v.begin() + 3, 99); //还原
v.erase(v.begin(), v.begin() + 3); //注意删除了3个元素而不是4个
for_each(v.begin(), v.end(), print);
v.pop_front();
v.pop_back();
for_each(v.begin(), v.end(), print);
//5. 查询
cout << v.front() << endl;
cout << v.back() << endl;
//危险的做法,但一般我们就像访问数组那样操作就行
for (int i = 15; i < 25; i++)
cout << "Element " << i << " is " << v[i] << endl;
//安全的做法
try
{
for (int i = 15; i < 25; i++)
cout << "Element " << i << " is " << v.at(i) << endl;
}
catch (out_of_range err) //#include <stdexcept>
{
cout << "out_of_range at " << i << endl;
}
//6. 清空
v.clear();
cout << v.size() << endl; //0
for_each(v.begin(), v.end(), print); //已经clear,v.begin()==v.end(),不会有任何结果。
return 0;
}
forward_list
实现了单链表,不可反转。相比于list,forward_list更简单,更紧凑,但功能也更少。
queue
queue是一个适配器类。queue模板让底层类(默认是deque)展示典型的队列接口。queue模板的限制比deque更多,它不仅不允许随机访问队列元素,甚至不允许遍历队列。与队列相同,只能将元素添加到队尾、从队首删除元素、查看队首和队尾的值、检查元素数目和测试队列是否为空。
priority_queue
priority_queue是另一个适配器类,支持的操作与queue相同。两者之间的主要区别在于,在priority_queue中,最大的元素被移到队首。内部区别在于,默认的底层类是vector。可以修改用于确定哪个元素放到队首的比较方式,方法是提供一个可选的构造函数参数:
priority_queue<int> pq1; // default version
priority_queue<int> pg2(greater<int>); // use greater<int> to order, greater<>函数是一个预定义的函数对象。
stack
与queue相似,stack也是一个适配器类,它给底层类(默认情况下为vector)提供了典型的栈接口。
关联容器(4个)
联容器主要有map和set。map是key-value形式的,set是单值。map和set只能存放唯一的key值,multimap和multiset可以存放多个相同的key值。底层都基于树型结构.
map/multimap
map容器提供一个键值对(key-value)容器,map与multimap差别仅仅在于multimap允许一个键对应多个值。对于迭代器来说,可以修改实值,而不能修改key。map会根据key自动排序。
//map有6个构造函数,涉及到内存分配,省略
//1.定义和初始化
map<int, string> map1; //空map
//2.常用操作方法
map1[3] = "Saniya"; //添加元素
map1.insert(pair<int, string>(1,"Siqinsini")); //pair方式插入元素
map1.insert(map<int, string> :: value_type(2, "Diyabi")); //value_type方式入元素
map1.insert(make_pair<int, string>(4, "V5") ); //make_pair插入元素
string str = map1[3]; //数值方式插入元素,根据key取得value,key不能修改
map<int, string> :: iterator iter_map = map1.begin();//取得迭代器首地址
int key = iter_map->first; //取得key
string value = iter_map->second; //取得value
map1.erase(iter_map); //删除迭代器数据
map1.erase(3); //根据key删除value
map1.size(); //元素个数
map1.empty(); //判断空
map1.clear(); //清空所有元素
//3.遍历
//正常遍历
for(map<int, string> :: iterator iter = map1.begin(); iter != map1.end(); iter++)
{
int k = iter->first;
string v = iter->second;
cout<< k << " " << v <<endl;
}
//反向遍历
for(map<int, string> :: iterator iter = mapStudent.rbegin(); iter != mapStudent.rend(); iter++)
cout << iter->first << " "<< iter->second << endl;
//数组方式遍历,注意此处不是for(int nindex = 0; nindex < nSize; nindex++)
for(int nindex = 1; nindex <= nSize; nindex++)
cout<<mapStudent[nindex]<<endl;
set/multiset
set的含义是集合,它是一个有序的容器,里面的元素都是排序好的支持插入、删除、查找等操作,就像一个集合一样,所有的操作都是严格在logn时间内完成,效率非常高。set和multiset的区别是,set插入的元素不能相同,但是multiset可以相同,set默认是自动排序的,使用方法类似list。
几种容器的比较
1)vector
内部数据结构:数组。
在末尾增加或者删除元素所需时间与元素数目无关,在中间或者开头增加或者删除元素所需时间是随元素数目呈线性变化。
2):deque
内部数据结构是:数组。
随机访问每个元素,所需要的时间为常量。在开头和末尾增加元素所需时间与元素数目无关,在中间增加或删除所需时间随元素数目呈线性变化。
3)list
内部数据结构:双向环状链表。
不能随机访问一个元素,可双向遍历,在开头,末尾和中间的任何地方增加或者删除元素所需时间都是常量。
4)set
键和值相等且唯一。
元素默认按升序排列、
5)map
键唯一,值对应键。
元素默认按键的升序排列
迭代器失效问题
关于迭代器失效的问题、小心使用STL中的erase。
//此段代码将crash
#include"stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> vect;
for(int i = 0; i < 10; i++ )
{
vect.push_back(i);
}
vector<int>::iterator iter = vect.begin();
for(; iter != vect.end(); iter++ )
{
if( *iter % 2 == 0 )
{
vect.erase(iter);
}
}
return 0;
}
程序直接crash了。iter是指向vector这个容器中的某个元素,如果不是在for、while循环中,erase删除元素是没有问题的,但是如果在for、while循环中对容器迭代,删除其中符合条件的所有元素,就可能出现问题。vect.erase(iter)之后,iter及其后面的迭代器已经失效了,不应该再使用这些迭代器了,再执行iter++,其行为是未定义的。其它容器也会遇到迭代器失效的问题。
对于vector被删除元素的迭代器以及指向后面元素的迭代器全部失效。对于deque在首部或尾部删除元素则只会使指向被删除元素的迭代器失效,其它位置的插入和删除操作将使该容器所有迭代器失效。
对于list仅有指向被删除元素的迭代器失效。为什么不同容器迭代器失效情况有差别呢?这主要与各容器的数据结构有关。如下例子:
#include "stdafx.h"
#include<iostream>
#include<vector>
#include<map>
#include<deque>
#include<list>
using namespace std;
int main()
{
//vector
vector<int> vect;
for(int i = 0; i < 10; i++ )
{
vect.push_back(i);
}
vector<int> :: iterator iter = vect.begin();
for(; iter != vect.end(); )
{
if( *iter % 2 == 0 )
{
//vect.erase(iter++); //测试,报错
iter = vect.erase(iter);
}
else
{
iter++;
}
}
//deque
deque<int> myDeque;
for( int i = 0; i < 10; i++ )
{
myDeque.push_back(i);
}
deque<int>::iterator deiter = myDeque.begin();
for(; deiter != myDeque.end();)
{
if( *deiter % 2 == 0 )
{
//myDeque.erase(deiter++); //测试,同样报错
deiter = myDeque.erase(deiter);
}
else
{
deiter++;
}
}
//list
list<int> myList;
for( int i = 0; i < 10; i++ )
{
myList.push_back(i);
}
list<int>::iterator listiter = myList.begin();
for(; listiter != myList.end();)
{
if( *listiter % 2 == 0 )
{
//myList.erase(listiter++);
listiter = myList.erase(listiter); //对于list这两种方式都可以的.因为对于list来说仅有指向被删除元素的迭代器失效,并不会导致所有的迭代器失效
}
else
{
listiter++;
}
}
//map
map<int, int> myMap;
myMap[0] = 1;
myMap[1] = 2;
myMap[2] = 3;
myMap[3] = 4;
map<int,int> :: iterator it = myMap.begin();
for(; it != myMap.end(); )
{
if( (it->second) % 2 == 0 )
{
myMap.erase(it++);
//it = myMap.erase(it); //对于map这两种方法都可以,C++11才开始支持返回当前的迭代器
}
else
{
it++;
}
}
//set跟map一样
system("pause");
return 0;
}