vector、set、map、list

一、vector
向量容器


(1)头文件 #include<vector>

(2)创建vector对象, vector<int> vec;

vector的元素不仅仅可以是int, double, string,还可以是结构体,但是要注意:结构体要定义为全局的,否则会出错。

(3)基本操作

使用下标访问元素,和数组一样下标从0开始
cout<<vec[0]<<endl; 
  
  

使用迭代器访问元素

  
  
  1. vector::iterator it;
  2. for(it = vec.begin(); it!=vec.end(); it++)
  3. cout<<*it<< endl;

插入元素 ,在pos位置插入一个elem元素
vec.insert(pos, elem)
   
   

删除pos位置的元素
vec.erase(pos)   
   
   

删除[begin, end)区间的元素
vec.erase(begin, end)   
   
   

向量大小 
vec.size();
   
   

清空  
vec.clear();  
   
   

传回第一个数据
vec.front()  
   
   

删除最后一个元素
vec.pop_back()  
   
   

尾部插入数据
vec.push_back(elem) 
   
   

重新设置该容器的大小
vec.resize(num)
   
   

返回指向容器第一个元素的迭代器
vec.begin()   
   
   

返回指向容器最后一个元素的迭代器
vec.end()  
   
   

判断容器是否为空
vec.empty() 
   
   

begin()  返回一个迭代器,它指向容器的第一个元素
end()  返回一个迭代器,它指向容器的最后一个元素的下一个位置
rbegin()  返回一个逆序迭代器,它指向容器的最后一个元素
rend()  返回一个逆序迭代器,它指向容器的第一个元素前面的位置

(4)算法

1、将元素翻转,需要头文件 #include<algorithm>
reverse(vec.begin(), vec.end())
 
 

2、排序,需要头文件 #include<algorithm>
sort(vec.begin(), vec.end());   默认是按升序排列,即从小到大
 
 

可以通过重写排序比较函数按照降序比较,如下:
定义排序比较函数:

 
 
  1. bool Comp(const int &a, const int &b)
  2. return a>b;
  3. 调用时:sort(vec.begin(), vec.end(), Comp),这样就实现了降序排序

3、填充元素,需要 头文件#include<algorithm>
fill函数的作用是:将一个区间的元素都赋予val值。函数参数:fill(first,last,val);//first为容器的首迭代器,last为容器的末迭代器,val为将要替换的值。

   
   
  1. # include <algorithm>
  2. fill(vec.begin(), vec.end(), val); //原来容器中每个元素被重置为val

fill_n函数的作用是:给你一个起始点,然后再给你一个数值count和val。把从起始点开始依次赋予count个元素val的值。 
注意: 不能在没有元素的空容器上调用fill_n函数 
fill_n(vec.begin, 10, val);  
   
   

4、删除vector中的重复元素

   
   
  1. vector< int> a={ 1, 4, 4, 3, 2, 4, 2};
  2. sort(a.begin(), a.end());
  3. vector< int>::iterator it = unique(a.begin(), a.end());
  4. a.erase(it, a.end());

先排序,然后使用unique的函数会将数组中重复的元素移到数组的尾部,并返回第一个重复元素移动到尾部后的位置(是一个迭代器)。
删除这第一个元素位置至数组最后一个元素之间的这些元素,即得到无重复元素的数组。
通过上述代码运行之后a数组变为{1, 2, 3, 4}。

注意:
1、for循环中,erase删除iter指向的元素,删除后iter指向被删除元素的下一个元素
2、resize()和reserve()函数的区别,首先要搞清楚size和capacity的区别,size返回的是容器中当前有的元素的个数,capacity是返回当前容器的内存大小。resize()是改变size的大小,如果resize(n),n比当前元素个数还小,会删除多出来的元素,如果大,那么会添加差值个数的初始化元素。reserve()是改变capacity的大小。

总结vector的特点:
(1)随机访问元素效率很高
(2)push_back的效率也会很高
(3)push_front的效率非常低,不建议使用
(4)insert需要把插入位置以后的元素全部后移,效率比较低,不建议使用
(5)erase需要把删除位置后面的元素全部前移,效率比较低,不建议使用
(6)当内存不够时,需要重新申请内存,再把以前的元素复制过来,效率也比较低

关于删除这里,坑很多,举几个例子,自行体会一下:
---------------使用迭代器进行删除----------------------
1、对于关联容器(如map, set, multimap, multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前的iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。
例子:

    
    
  1. set <int> valset = { 1,2,3,4,5,6 };
  2. set <int>::iterator iter;
  3. for (iter = valset.begin(); iter != valset.end(); )
  4. {
  5. if (3 == *iter)
  6. valset.erase(iter++);
  7. else
  8. ++iter;
  9. }

因为传给erase的是iter的一个副本,iter++是下一个有效的迭代器

2、对于序列式容器(如vector,deque,list等),删除当前的iterator会使后面所有元素的iterator都失效。这是因为vector,deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。不过erase方法可以返回下一个有效的iterator。

例子:

    
    
  1. vector< int> val = { 1, 2, 3, 4, 5, 6};
  2. vector< int>::iterator iter;
  3. for(iter = val.begin();iter != val.end();){
  4. cout<<*iter<< endl;
  5. if( 3 == *iter){
  6. iter = val.erase(iter); //返回下一个有效的迭代器,无需+1
  7. } else{
  8. ++iter;
  9. }
  10. }

释放vector内存
当vector、string大量插入数据后,即使删除了大量数据(或者全部都删除,即clear)并没有改变容器的容量(capacity),所以仍然会占着内存。为了避免这种情况,我们应该想办法改变容器的容量使之尽可能小的符合当前数据所需。
《effective STL》给出的解决方案是

   
   
  1. vector<type> v;
  2. //.....这里添加许多元素给v
  3. //......这里删除v中的许多元素
  4. vector<type>(v).swap(v);
  5. //此时v的容量已经尽可能的符合其当前包含的元素数量
  6. //对于string则可能像下面这样
  7. string(s).swap(s)

即先创建一个临时拷贝与原先的vector一致,值得注意的是,此时的拷贝其容量是尽可能小的符合所需数据的。紧接着将该拷贝与原先的vector v进行交换。好了此时,执行交换时,临时变量会被销毁,内存得到释放。此时的v即为原先的临时拷贝,而交换后的临时拷贝则为容量非常大的vector(不过已经被销毁)

一个验证的例子


   
   
  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. vector <string> v;
  5. char ch;
  6. int main()
  7. {
  8. for(int i=0;i <1000000;i++){
  9. v.push_back("abcdefghijklmn");
  10. }
  11. cout<<v.capacity()<<endl;
  12. //此时检查内存
  13. v.erase(v.begin(), v.begin()+999900);
  14. cout<<v.capacity()<<endl;
  15. vector<string> (v).swap(v);
  16. cout <<v.capacity()<<endl;
  17. return 0;
  18. }




输出:
1048576
1048576
100

上面这种方法虽然释放了内存,但是同时也增加了拷贝数据的时间消耗,不过一般重新调整容量的情况都是vector本身元素较少的情况,所以时间消耗可以忽略不计。

二、set

(1) 头文件:#include<set>

(2)创建一个set对象:set<int> iset

(3)常用的方法

begin()   返回set容器的第一个元素
end()  返回set容器的最后一个元素
clear()  删除set容器中的所有元素
empty()  判断set容器是否为空
max_size()  返回set容器可能包含的元素最大个数
size()   返回当前set容器中的元素个数

(4)函数

count()   用来查找set中某个键值出现的次数。因为set中不会出现重复的元素,因此该函数也就变成了判断某一键值是否在set中出现过

erase(iterator), 删除定位器iterator指向的值
erase(first, second)   删除定位器first和second之间的值
erase(key_value)    删除键值key_value的值

find(),   返回给定值的定位器,如果没找到则返回end()

insert(key_value)    将key_value插入到set中,返回值是pair ::iterator, bool>,bool标志着插入是否成功,而iterator代表插入的位置,若key_value已经在set中,则iterator表示的key_value在set中的位置。

insert(first, second) 将定位器first到second之间的元素插入到set中,返回值是void

lower_bound(key_value)   返回第一个大于等于key_value的定位器

upper_bound(key_value)  返回最后一个大于等于key_value的定位器


三、map

(1)头文件 #include<map>

(2)创建map对象   map<int, string> m;

(3)基本操作


1、我们可以使用find()和count()方法来发现一个键是否存在。

查找map中是否包含某个关键字条目用find()方法,传入的参数是要查找的key

  • begin()   返回指向map头部的迭代器,注意返回的数据类型是iterator
  • end()    返回指向map末尾的迭代器,注意返回的数据类型是iterator
  • find()   查找一个元素
  • count()   返回指定元素出现的次数

例子:


 
 
  1. int key= 2; //要查找的key
  2. //定义一个条目变量(实际是指针)
  3. map< int, string>::iterator it;
  4. it = map1.find(key);
  5. if(it == map1.end()){
  6. //没找到
  7. }
  8. else{
  9. //找到
  10. }

或者


 
 
  1. map< string, string>m;
  2. if(m[ 112] == "")
  3. cout<< "we do not find 112"<< endl;


通过map对象的方法获取的iterator数据类型是一个std::pair对象,包括两个数据iterator->first和iterator->second分别代表关键字和存储的数据


2、从map中删除元素

移除某个map中某个条目用erase()

该方法的定义如下:

  
  
  1. iterator erase(iterator it); //通过一个条目对象删除
  2. iterator erase(iterator first, iterator last); //删除一个范围
  3. size_type erase(const Key& key); //通过关键字删除

clear()    删除所有元素,就相当于map1.erase(map1.begin(), map1.end())
例子: 删除112

   
   
  1. map< int, string>::iterator it;
  2. it = map1.find( 112);
  3. if(it == map1.end())
  4. cout<< "we do not find 112"<< endl;
  5. else
  6. map1.erase(it);

3、添加元素
insert()插入元素
例子:

  
  
  1. map< int, string> map1;
  2. map1.insert(pair< int, string>( 102, "aclive"));
  3. map1.insert( map< int, string>::value_type( 321, "hai"));
  4. map1[ 112]= "April"; //map中最简单最常见的插入添加

empty()    如果map为空则返回true
erase() 删除一个元素

size() 返回map中元素的个数

rend()返回一个指向map尾部的逆向迭代器


4、swap的用法

map中的swap不是一个容器中的元素交换,而是两个容器交换


5、map中的元素时自动按key升序排序,所以不能对map用sort函数。


6、map和unordered_map

unordered_map是C++ Boost库中的内容,这里的unordered翻译成“无序”。

但它并不是完全的“无序”的概念,而是散列式的存储方式。

unordered库提供了两个散列映射类,unordered_map和unordered_multimap。

它们的接口、用法与STL里的标准关联容器map和multimap相同,但是内部实现不同。

它们用散列表代替了二叉树的实现,模板参数多了散列计算函数,比较谓词使用equal_to<>。

看到这里,我们就应该明白,比起map/multimap,unordered_map和unordered_mutimap在查找元素的时候,速度不是一般的快。

它们的查找速率是常数级的,而map/multimap是基于二叉树实现的,所以查找是O(logn)


unordered库使用“桶”来存储元素,散列值相同的被存储在一个桶里。当散列容器中有大量数据时,同一个桶里的数据也会增多,造成访问冲突,降低性能。为了提高散列容器的性能,unordered库会在插入元素是自动增加桶的数量,不需要用户指定。但是,用户也可以在构造函数或者rehash()函数中,指定最小的桶的数量。


例子:

map代码:


 
 
  1. #include<string>
  2. #include<iostream>
  3. #include<map>
  4. using namespace std;
  5. struct person
  6. {
  7. string name;
  8. int age;
  9. person( string name, int age)
  10. {
  11. this->name = name;
  12. this->age = age;
  13. }
  14. bool operator < ( const person& p) const
  15. {
  16. return this->age < p.age;
  17. }
  18. };
  19. map<person, int> m;
  20. int main()
  21. {
  22. person p1("Tom1",20);
  23. person p2("Tom2",22);
  24. person p3("Tom3",22);
  25. person p4("Tom4",23);
  26. person p5("Tom5",24);
  27. m.insert(make_pair(p3, 100));
  28. m.insert(make_pair(p4, 100));
  29. m.insert(make_pair(p5, 100));
  30. m.insert(make_pair(p1, 100));
  31. m.insert(make_pair(p2, 100));
  32. for( map<person, int>::iterator iter = m.begin(); iter != m.end(); iter++)
  33. {
  34. cout<<iter->first.name<< "\t"<<iter->first.age<< endl;
  35. }
  36. return 0;
  37. }

unordered_map代码:


 
 
  1. #include<string>
  2. #include<iostream>
  3. #include<boost/unordered_map.hpp>
  4. using namespace std;
  5. struct person
  6. {
  7. string name;
  8. int age;
  9. person( string name, int age)
  10. {
  11. this->name = name;
  12. this->age = age;
  13. }
  14. bool operator== ( const person& p) const
  15. {
  16. return name==p.name && age==p.age;
  17. }
  18. };
  19. size_t hash_value( const person& p)
  20. {
  21. size_t seed = 0;
  22. boost::hash_combine(seed, boost::hash_value(p.name));
  23. boost::hash_combine(seed, boost::hash_value(p.age));
  24. return seed;
  25. }
  26. int main()
  27. {
  28. typedef boost:: unordered_map<person, int> umap;
  29. umap m;
  30. person p1("Tom1",20);
  31. person p2("Tom2",22);
  32. person p3("Tom3",22);
  33. person p4("Tom4",23);
  34. person p5("Tom5",24);
  35. m.insert(umap::value_type(p3, 100));
  36. m.insert(umap::value_type(p4, 100));
  37. m.insert(umap::value_type(p5, 100));
  38. m.insert(umap::value_type(p1, 100));
  39. m.insert(umap::value_type(p2, 100));
  40. for(umap::iterator iter = m.begin(); iter != m.end(); iter++)
  41. {
  42. cout<<iter->first.name<< "\t"<<iter->first.age<< endl;
  43. }
  44. return 0;
  45. }

前者的输出结果为:

Tom1    20
Tom3    22
Tom4    23
Tom5    24

后者的输出结果为:

Tom1    20
Tom5    24
Tom4    23
Tom2    22
Tom3    22


特性对比:

运行效率方面:unordered_map最高,map效率较低但提供了稳定效率和有序的序列。

占用内存方面:map内存占用略低,unordered_map内存占用略高,而且是线性成比例的。

若考虑有序,查询速度稳定,容器元素量少于1000,非频繁查询那么考虑使用map。
若非常高频查询(100个元素以上,unordered_map都会比map快),内部元素可非有序,数据大超过1k甚至几十万上百万时候就要考虑使用unordered_map


c++ STL容器类的模板主要有:


头文件内容
<vector>vector<T>容器表示一个在必要时可自动增加容量的数组,只能在矢量容器的末尾添加新元素
<deque>deque<T>容器实现一个双端队列。它等价于一个矢量,不过增加了向容器开头添加元素的能力
<list>list<T>容器是一个双向链表
<map>map<K, T>是一个关联容器,用关联链(类型为K)存储确定链/对象对所在位置的每个对象(类型为T)。
映射中的每个键的值必须唯一。
这个头文件也定义 multimap<K, T>容器,其中键/对象对中的键不需要唯一
<set>set<T>容器是一个映射,其中各对象作为自身的键。集合中的所有对象必须唯一,使用对象作为自身的
键的一个后果是无法在集合中修改对象;
要修改对象,必须先删除它,然后插入修改后的版本。
<bitset>定义表示固定位数的bitset<T>类模板。它通常用来存储表示一组状态或条件的标记(flag)


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值