史上最全的C++ STL容器解析

文章目录

  • STL的六大组件
    • 一、 浅谈 vector 容器
          • 1. vector 容器的概念
          • 2. vector容器的声明
          • 3. vector容器的使用方法
    • 二、 浅谈 queue 容器
          • 1. queue 容器的概念
          • 2. queue 容器的声明
          • 3. queue容器的使用方法
    • 三、 浅谈 stack 容器
          • 1. stack 容器的概念
          • 2. stack 容器的声明
          • 3. stack 容器的使用方法
    • 四、 浅谈 string 容器
          • 1. string 容器的概念
          • 2. string 容器的使用方法及传统字符读入的对比
    • 五、 浅谈 priority_queue 容器
          • 1. priority_queue 容器的概念
          • 2. priority_queue 容器的声明
          • 3. priority_queue 容器的使用方法
    • 六、 浅谈 deque 容器
          • 1. deque 容器的概念
          • 2. deque 容器的声明
          • 3. deque 容器的使用方法
    • 七、 浅谈 set 容器
          • 1. set 容器的概念
          • 2. set 容器的声明
          • 3. set 容器的使用方法
    • 八、 浅谈 multiset 容器
          • 1. multiset 容器的概念
          • 2. 与set容器不太一样的地方
          • 3. multiset 容器的使用方法
    • 九、 浅谈 bitset 容器
          • 1. bitset 容器的概念
          • 2. bitset 容器的声明
          • 3. bitset 容器的使用方法
          • 4. 位运算操作在bitset中的实现
          • 5. bitset容器的实际应用
    • 十、 浅谈 map 容器
          • 1. map 容器的概念
          • 2. map 容器的声明
          • 3. map 容器的使用方法
          • 4. map 和 pair 的关系
    • 十一、 浅谈 map与unordered_map区别及使用
          • 1. 需要引入的头文件不同
          • 2. 内部实现机理不同
          • 3. 优缺点以及适用处
          • 4. unordered_map 常用操作汇总举例
          • 5. unordered_set 与 set
    • 十二、 浅谈 list 容器
          • 1. list 容器的概念
          • 2. list 容器的声明
          • 3. list 容器的使用方法
    • 十三、 浅谈 forward_list 容器
          • 1. forward_list 容器的概念
          • 2. forward_list 容器的特性
          • 3. forward_list 容器的使用方法

STL的六大组件

史上最全的C++ STL容器解析_第1张图片

一、 浅谈 vector 容器

1. vector 容器的概念

我们知道,一个数组必须要有固定的长度,在开一个数组的时候,这个长度也就被静态地确定下来了。但是vector却是数组的“加强版”,对于一组数据来讲,你往vector里存多少数据,vector的长度就有多大。也就是说,我们可以将其理解为一个“变长数组”。
事实上,vector的实现方式是基于倍增思想的:假如vector的实际长度为n,m为vector当前的最大长度,那么在加入一个元素的时候,先看一下,假如当前的n=m,则再动态申请一个2m大小的内存。反之,在删除的时候,如果n<=m/2,则再释放一半的内存。

2. vector容器的声明

vector容器存放在模板库:#include里,使用前需要先开这个库。
容器类型<变量类型> 名称

例如:

#include
vector<int> vec;
vector<char> vec;
vector<pair<int,int> > vec;
vector<node> vec;
struct node{...};
3. vector容器的使用方法
用法 作用
vec.begin(),vec.end() 返回 vector 的首、尾迭代器
vec.front(), vec.back() 返回 vector 的首、尾元素
vec.size() 返回vector当前的长度(大小)
vector.push_back() 从vector的末尾加入一个元素
vec.pop_back() 从vector末尾删除一个元素
vec.empty() 返回vector是否为空,1为空、0不为空
vec.clear() 清空vector

【注意】
除了上面说过的那些之外,我们的vector容器是支持随机访问的,即可以像数组一样用[]来取值。请记住,不是所有的STL容器都有这个性质!在STL的学习过程中,一定要清楚各个容器之间的异同!


二、 浅谈 queue 容器

1. queue 容器的概念

queue 在英文中是队列的意思。队列是一种基本的数据结构。而C++STL中的队列就是把这种数据结构模板化了。我们可以在脑中想象买票时人们站的排队队列。我们发现,在一个队列中,只可以从队首离开,从队尾进来(没有插队,想啥呢)。即一个先进先出的数据结构。
史上最全的C++ STL容器解析_第2张图片

2. queue 容器的声明

queue容器存放在模板库:#include里,使用前需要先开这个库。
容器类型<变量类型> 名称

例:

#include

queue<int> q;

queue<char> q;

queue<pair<int,int> > q;

queue<node> q;

struct node{...};
3. queue容器的使用方法
用法 作用
q.front(), q.back() 返回 queue 的首、尾元素
q.push() 从queue末尾加入一个元素
q.size() 返回queue当前的长度(大小)
q.pop() 从queue末尾删除一个元素
q.empty() 返回queue是否为空,1为空、0不为空

【注意】
queue 不支持随机访问,即不能像数组一样地任意取值。并且,queue并不支持全部的vector的内置函数。比如queue不可以用clear()函数清空,清空queue必须一个一个弹出。同样,queue也并不支持遍历,无论是数组型遍历还是迭代器型遍历统统不支持,所以没有begin(),end();函数,使用的时候一定要清楚异同!


三、 浅谈 stack 容器

1. stack 容器的概念

stack 在英文中是栈的意思。栈是一种基本的数据结构。而C++STL中的栈就是把这种数据结构模板化了。
栈的示意图如下:这是一个先进后出的数据结构。这非常重要!!
史上最全的C++ STL容器解析_第3张图片
事实上,stack容器并不是一种标准的数据结构,它其实是一个容器适配器,里面还可以存其他的STL容器。但那种使用方法过于高深而且不是很常用,所以在此不与介绍。请有兴趣的读者自行查询资料。

2. stack 容器的声明

stack容器存放在模板库:#include里,使用前需要先开这个库。
容器类型<变量类型> 名称

例:

#include
stack<int> st;
stack<char> st;
stack<pair<int,int> > st;
stack<node> st;
struct node{...};
3. stack 容器的使用方法
用法 作用
st.top() 返回stack的栈顶元素
st.push() 从stack栈顶加入一个元素
st.size() 返回stack当前的长度(大小)
st.pop() 从stack栈顶弹出一个元素
st.empty() 返回stack是否为空,1为空、0不为空

**【注意】**不要把queue的获取队头元素和stack的获取栈顶元素两个方法混淆


四、 浅谈 string 容器

1. string 容器的概念

其实string并不是STL的一种容器,但是由于它的使用方法等等和STL容器很像,所以就把它当作STL容器一样介绍。
其实string容器就是个字符串,这通过它的英文译名就能看得出来。但是对于字符串以及字符串的相关操作,可能读者还是对普通的C/C++的#include,#include库更熟悉一些。我丝毫不否认这些传统字符操作的经典性和实用性,但是由于它们函数定义的局限,有些时候对于一些特殊的读入、输出、遍历等要求,它的操作并不如string容器好用。
比如,要求读入一群中间可能带空格的字符串,如果用传统方式进行读入,可能就会很麻烦,但是如果使用string的话,一个读入函数就可以完全搞定。

2. string 容器的使用方法及传统字符读入的对比
操作 string 字符阵列
声明字符串 string s; char s[100];
取得第i个字符 s[i] s[i]
字符串长度 s.length(); s.size() strlen(s) 不计\0
读取一行 getline(cin,s); gets(s);
字符串赋值 s = “hello”; strcpy(s, “hello”);
字符串拼接 s += “world”; strcat(s, “world”);
字符串比较 s == “hello” strcmp(s, “hello”)

五、 浅谈 priority_queue 容器

1. priority_queue 容器的概念
  • priority_queue 在英文中是优先队列的意思。
  • 队列是一种基本的数据结构。其实现的基本示意图如下所示:
    史上最全的C++ STL容器解析_第4张图片
  • 而C++STL中的优先队列就是在这个队列的基础上,把其中的元素加以排序。其内部实现是一个二叉堆(默认是最大堆)。所以优先队列其实就是把堆模板化,将所有入队的元素排成具有单调性的一队,方便我们调用。
2. priority_queue 容器的声明
  • priority_queue 容器存放在模板库:#include里,使用前需要先开这个库。
  • 这里需要注意的是,优先队列的声明与一般STL模板的声明方式并不一样。事实上,我认为其是C++STL中最难声明的一个容器。

(1)【大根堆声明方式】
大根堆就是把大的元素放在堆顶的堆。优先队列默认实现的就是大根堆,所以大根堆的声明不需要任何花花肠子,直接按C++STL的声明规则声明即可。

#include 
priority_queue<int> q;
priority_queue<string> q;
priority_queue<pair<int, int> > q;

【注意】C++ 中的int,string等类型可以直接比较大小,所以不用我们多操心,优先队列自然会帮我们实现。但是如果是我们自己定义的结构体,就需要进行重载运算符了。

(2)【小根堆声明方式】

  • 大根堆是把大的元素放堆顶,小根堆就是把小的元素放到堆顶。
  • 实现小根堆有两种方式:
  1. 因为优先队列默认实现的是大根堆,所以我们可以把元素取反放进去,因为负数的绝对值越小越大,那么绝对值较小的元素就会放在前面,我们在取出的时候再取个反,就瞒天过海地用大根堆实现了小根堆。
  2. 小根堆有自己的声明方式,但是比较复杂
    priority_queue, greater > q;
    【注意】当我们声明的时候碰到两个"<“或者”>"放在一起的时候,一定要记得在中间加一个空格。这样编译器才不会把两个连在一起的符号判断成位运算的左移/右移。
3. priority_queue 容器的使用方法
用法 作用
q.top() 返回priority_queue的首元素
q.push() 从priority_queue末尾加入一个元素
q.size() 返回priority_queue当前的长度(大小)
q.pop() 从priority_queue末尾删除一个元素
q.empty() 返回priority_queue是否为空,1为空、0不为空

**【注意】**priority_queue取出队首元素是使用top,而不是front,这点一定要注意!!这一点与queue不同


六、 浅谈 deque 容器

1. deque 容器的概念
  • deque的意义是:双端队列。队列是我们常用而且必须需要掌握的数据结构。C++STL中的确有模拟队列的模板:#include中的queue和priority_queue。队列的性质是先进先出,即从队尾入队,从队首出队。而deque的特点则是双端进出,即处于双端队列中的元素既可以从队首进/出队,也可以从队尾进/出队。
  • 即:deque是一个支持在两端高效插入、删除元素的线性容器。
  • deque模板存储在C++STL的#include中。
2. deque 容器的声明
#include 
deque<int> dq;
deque<int> dq1(4, 100);  //four ints with value 100
deque<int> dq2(dq1.begin(), dq1.end());
deque<int> dq3(dq2); //a copy of dq2

int myints[] = {16,2,77,29};
deque<int> dq4(myints, myints + sizeof(myints) / sizeof(int) );
3. deque 容器的使用方法
用法 作用
q.begin(), q.end() 返回deque的首、尾迭代器
q.front(), q.back() 返回deque的首、尾元素
q.push_front() 从队头入队一个元素
q.push_back() 从队尾入队一个元素
q.pop_front() 从队头出队一个元素
q.pop_back() 从队尾出队一个元素
q.size() 返回队列中元素个数
q.clear() 清空队列

**【注意】**除了这些用法之外,deque比queue更优秀的一个性质是它支持随机访问,即可以像数组下标一样取出其中的一个元素。即:q[i]


七、 浅谈 set 容器

1. set 容器的概念
  • set在英文中的意义是:集合。set容器也的确“人如其名”,实现了这个集合的功用。
  • 高中数学必修一集合那章(高一以下的小伙伴不用慌,不讲数学只讲概念),关于集合的性质,给出了三个概念:无序性、互异性、确定性。
  • 那么,set容器的功用就是维护一个集合,其中的元素满足互异性。
  • 我们可以将其理解为一个数组。这个数组的元素是两两不同的。
  • 这个两两不同是指,如果这个set容器中已经包含了一个元素i,那么无论我们后续再往里假如多少个i,这个set中还是只有一个元素i,而不会出现一堆i的情况。这就为我们提供了很多方便。
  • 但是,需要额外说明的是,刚刚说集合是有无序性的,但是set中的元素是默认排好序(按升序排列)的。(稍微说一句,set容器自动有序和快速添加、删除的性质是由其内部实现:红黑树(平衡树的一种)。这个东西过于高深我不会,所以不予过多介绍,有兴趣的小伙伴可以自行浏览相关内容。)
2. set 容器的声明

set容器的声明和大部分C++STL容器一样,都是:容器名<变量类型> 名称的结构。前提需要开#include库。如:

#include 
set<int> s;
set<char> s;
set<pair<int, int> > s;
set<node> s;
struct node{...};
3. set 容器的使用方法
用法 作用
s.begin(), s.end() 返回集合的首尾迭代器
s.insert(k) 向集合中加入元素k
s.earse(k) 删除集合中元素k
s.size() 返回当前集合的元素个数。
s.find(k) 返回集合中指向元素k的迭代器。如果不存在这个元素,就返回s.end()
s.empty() 返回当前集合是否为空,是返回1,否则返回0
s.clear() 清空当前集合。

八、 浅谈 multiset 容器

1. multiset 容器的概念
  • set在英文中的意义是:集合。而multi−前缀则表示:多重的。所以multiset容器就叫做:有序多重集合。
  • multiset 的很多性质和使用方式和set容器差不了多少。而multiset容器在概念上与set容器不同的地方就是:set的元素互不相同,而multiset的元素可以允许相同。
2. 与set容器不太一样的地方

s.erase(k);

  • erase(k)函数在set容器中表示删除集合中元素k。但在multiset容器中表示删除所有等于k的元素。
  • 那么,会存在一种情况,我只想删除这些元素中的一个元素,怎么办呢?
    if((it = find(k)) != s.end())
    s.erase(it);
    if中的条件语句表示定义了一个指向一个a元素迭代器,如果这个迭代器不等于s.end(),就说明这个元素的确存在,就可以直接删除这个迭代器指向的元素了。

s.count(k);
count(k) 函数返回集合中元素k的个数。set容器中并不存在这种操作。这是multiset独有的。


3. multiset 容器的使用方法
// multiset::insert (C++98)
#include 
#include 

int main ()
{
  std::multiset<int> mymultiset;
  std::multiset<int>::iterator it;

  // set some initial values:
  for (int i=1; i<=5; i++) mymultiset.insert(i*10);  // 10 20 30 40 50

  it=mymultiset.insert(25);

  it=mymultiset.insert (it,27);    // max efficiency inserting
  it=mymultiset.insert (it,29);    // max efficiency inserting
  it=mymultiset.insert (it,24);    // no max efficiency inserting (24<29)

  int myints[]= {5,10,15};
  mymultiset.insert (myints,myints+3);

  std::cout << "mymultiset contains:";
  for (it=mymultiset.begin(); it!=mymultiset.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}

【Output:】

myset contains: 5 10 10 15 20 24 25 27 29 30 40 50

九、 浅谈 bitset 容器

1. bitset 容器的概念

bitset 容器其实就是个01串。可以被看作是一个bool数组。它比bool数组更优秀的优点是:节约空间,节约时间,支持基本的位运算。在bitset容器中,8位占一个字节,相比于bool数组4位一个字节的空间利用率要高很多。同时,n位的bitset在执行一次位运算的复杂度可以被看作是n/32,这都是bool数组所没有的优秀性质。

2. bitset 容器的声明

因为bitset容器就是装01串的,所以不用在< >中装数据类型,这和一般的STL容器不太一样。< >中装01串的位数。
如:(声明一个10^5 位的 bitset)
bitset<100000> s;

3. bitset 容器的使用方法
用法 作用
s.count() 返回 s中有多少个 1
s.any() / s.none() 如果 bitset中全部为0,那么 s.any()返回false,s.none() 返回true;反之,假如bitset中至少有一个1,哪怕只有一个1,那么s.any()返回true,s.none() 返回false
s.set() / s.set(u, v) 不带参的set()是把bitset全部置为1,带参的set是把bitset中的第u位变成v, v = 0/1
s.reset() / s.reset(k) 与set()函数相对地,reset()函数将bitset的所有位置为0。而reset()函数只传一个参数,表示把这一位改成0
s.filp / s.filp(k) 将整个bitset容器按位取反。其传入的参数表示把其中一位取反
q.empty() 返回queue是否为空,1为空、0不为空
4. 位运算操作在bitset中的实现

bitset的作用就是帮助我们方便地实现位运算的相关操作。它当然支持位运算的一些操作内容。我们在编写程序的时候对数进行的二进制运算均可以用在bitset函数上。

符号 作用
~ 按位取反
& 按位与
| 按位或
^ 按位异或
<< | >> 左移 / 右移
== | != 比较两个bitset是否相等

[补充]
bitset容器还支持直接取值和直接赋值的操作:具体操作方式如下:
s[3]=1; s[5]=0;
[注意]
在bitset容器中,最低位为0。这与我们的数组实现仍然有区别。

5. bitset容器的实际应用

bitset可以高效率地对01串,01矩阵等等只含0/1的题目进行处理。其中支持的许多操作对我们处理数据非常有帮助。如果碰到一道0/1题,使用bitset或许是不错的选择。


十、 浅谈 map 容器

1. map 容器的概念

map 的英语释义是“地图”,但map容器可和地图没什么关系。map是“映射容器”,其存储的两个变量构成了一个键值到元素的映射关系。
比如下图:
史上最全的C++ STL容器解析_第5张图片
我们可以根据键值快速地找到这个映射出的数据。
map 容器的内部实现是一棵红黑树(平衡树的一种),因为比较复杂而且与理解并无多大关系,所以不予介绍,有兴趣的读者可以自己查阅相关的资料。

2. map 容器的声明

map容器存在于STL模板库#include中。使用的时候需要先开这个库。
#include
map mp;
这个mp可用来统计每个字符出现的次数

3. map 容器的使用方法

因为map容器和set容器都是使用红黑树作为内部结构实现的。所以其用法比较相似。但由于二者用途大有不同,所以其用途还有微妙的差别。对于初学者来讲,其更容易涉及到的应该是vector容器、queue容器等,但是对于大佬们,经常用个set、map,没事再用bitset压一压状态这都是家常便饭。

用法 作用
m.begin(), m.end() 返回 m的首、尾迭代器
m.insert() (1)可以 m[‘a’] = 1进行赋值;(2)m.insert({‘b’, 2});
m.erase(迭代器) / m.erase(键) / m.erase(迭代器范围) (1)m.erase(it); (2) m.erase(‘b’); (3) m.erase(it, m.end())
it->first / it->second 用于map的遍历:it->first获取key值,it->second获取value值
m.find(键值) m.find(k); 返回的键值为k的元素的迭代器
m.empty() 判断m是否为空,是空返回true,否则返回false
m.clear() 清除m中的元素

【其他】

  1. m.lower_bound(key) / m.upper_bound(key)
  • m.lower_bound(key):返回map中第一个大于或等于key的迭代器
  • m.upper_bound(key):返回map中第一个大于key的迭代器
// map::lower_bound/upper_bound
#include 
#include 

int main ()
{
  std::map<char,int> mymap;
  std::map<char,int>::iterator itlow,itup;

  mymap['a']=20;
  mymap['b']=40;
  mymap['c']=60;
  mymap['d']=80;
  mymap['e']=100;

  itlow=mymap.lower_bound ('b');  // itlow points to b
  itup=mymap.upper_bound ('d');   // itup points to e (not d!)

  mymap.erase(itlow,itup);        // erases [itlow,itup)

  // print content:
  for (std::map<char,int>::iterator it=mymap.begin(); it!=mymap.end(); ++it)
    std::cout << it->first << " => " << it->second << '\n';

  return 0;
}

【运行结果】
在这里插入图片描述
2. m.equal_range()

  • equal_range是C++ STL中的一种二分查找的算法,试图在已排序的[first,last)中寻找value,它返回一对迭代器i和j,其中i是在不破坏次序的前提下,value可插入的第一个位置(亦即lower_bound),j则是在不破坏次序的前提下,value可插入的最后一个位置(亦即upper_bound),因此,[i,j)内的每个元素都等同于value,而且[i,j)是[first,last)之中符合此一性质的最大子区间
  • 如果以稍许不同的角度来思考equal_range,我们可把它想成是[first,last)内"与value等同"之所有元素形成的区间A,由于[fist,last)有序(sorted),所以我们知道"与value等同"之所有元素一定都相邻,于是,算法lower_bound返回区间A的第一个迭代器,算法upper_bound返回区间A的最后一个元素的下一个位置,算法equal_range则是以pair的形式将两者都返回
  • 即使[fist,last)并未含有"与value等同"之任何元素,以上叙述仍然合理,这种情况下,"与value等同"之所有元素形成的,其实是一个空区间,在不破坏次序的情况下,只有一个位置可以插入value,而equal_range所返回的pair,其第一和第二(都是迭代器)皆指向该位置。
// map::equal_range
#include 
#include 

int main ()
{
  std::map<char,int> mymap;

  mymap['a']=10;
  mymap['b']=20;
  mymap['c']=30;

  std::pair<std::map<char,int>::iterator,std::map<char,int>::iterator> ret;
  ret = mymap.equal_range('b');

  std::cout << "lower bound points to: ";
  std::cout << ret.first->first << " => " << ret.first->second << '\n';

  std::cout << "upper bound points to: ";
  std::cout << ret.second->first << " => " << ret.second->second << '\n';

  return 0;
}

在这里插入图片描述

4. map 和 pair 的关系

我们发现,map和C++内置二元组pair特别相似。那是不是map就是pair呢?(当然不是)
首先,map构建的关系是映射,也就是说,如果我们想查询一个键值,那么只会返回唯一的一个对应值。但是如果使用pair的话,不仅不支持O(log)级别的查找,也不支持知一求一,因为pair的第一维可以有很多一样的,也就是说,可能会造成一个键值对应n多个对应值的情况。这显然不符合映射的概念。


十一、 浅谈 map与unordered_map区别及使用

1. 需要引入的头文件不同
map: #include < map >
unordered_map: #include < unordered_map >
2. 内部实现机理不同
  • map:map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来
  • unordered_map:unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的
3. 优缺点以及适用处

[map]

  • 优点:有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作。红黑树,内部实现一个红黑树使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率非常的高
  • 缺点:空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间
  • 适用点:对于那些有顺序要求的问题,用map会更高效一些

【unordered_map】

  • 优点:因为内部实现了哈希表,因此其查找速度非常的快
  • 缺点:哈希表的建立比较耗费时间
  • 适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map

[总结]

  1. 内存占有率的问题就转化成红黑树 VS hash表 , 还是unordered_map占用的内存要高。但是unordered_map执行效率要比map高很多。
  2. 对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的
  3. unordered_map的用法和map是一样的,提供了 insert,size,count等操作,并且里面的元素也是以pair类型来存贮的。其底层实现是完全不同的,上方已经解释了,但是就外部使用来说却是一致的。
4. unordered_map 常用操作汇总举例
#include   
#include   
#include 
#include   
using namespace std;  
int main()  
{  
	//注意:C++11才开始支持括号初始化
    unordered_map<int, string> myMap={{ 5, "张大" },{ 6, "李五" }};//使用{}赋值
    myMap[2] = "李四";  //使用[ ]进行单个插入,若已存在键值2,则赋值修改,若无则插入。
    myMap.insert(pair<int, string>(3, "陈二"));//使用insert和pair插入
  
	//遍历输出+迭代器的使用
    auto iter = myMap.begin();//auto自动识别为迭代器类型unordered_map::iterator
    while (iter!= myMap.end())
    {  
        cout << iter->first << "," << iter->second << endl;  
        ++iter;  
    }  
	
	//查找元素并输出+迭代器的使用
    auto iterator = myMap.find(2);//find()返回一个指向2的迭代器
    if (iterator != myMap.end())
	    cout << endl<< iterator->first << "," << iterator->second << endl;  
    system("pause");  
    return 0;  
}  

【运行结果】
史上最全的C++ STL容器解析_第6张图片
【若将 unordered_map 改为 map,结果如下】
史上最全的C++ STL容器解析_第7张图片

5. unordered_set 与 set

unordered_set 与 set 区别类似于unordered_map 与 map,set与map内部都是基于红黑树实现的,都是有序的。而unordered_map与unordered_set都是基于哈希表实现的,都无序
下列例子对比了set与unordered_set的使用

// unordered_set::insert
#include 
#include 
#include 
#include 
#include 
int main ()
{
  std::set<std::string> myset = {"yellow","green","blue"};
  std::unordered_set<std::string> myset1 = {"yellow","green","blue"};
  std::array<std::string,2> myarray = {"black","white"};
  std::string mystring = "red";

  myset.insert (mystring);                        // copy insertion
  myset.insert (mystring+"dish");                 // move insertion
  myset.insert (myarray.begin(), myarray.end());  // range insertion
  myset.insert ( {"purple","orange"} );           // initializer list insertion

  myset1.insert (mystring);                        // copy insertion
  myset1.insert (mystring+"dish");                 // move insertion
  myset1.insert (myarray.begin(), myarray.end());  // range insertion
  myset1.insert ( {"purple","orange"} );           // initializer list insertion


  std::cout << "set contains:";
  for (const std::string& x: myset) std::cout << " " << x;
  std::cout <<  std::endl;

  std::cout << "unordered_set contains:";
  for (const std::string& x: myset1) std::cout << " " << x;
  std::cout <<  std::endl;

  return 0;
}

【运行结果】可以发现,set里面的元素始终都是有序的,但是unordered_set里面的顺序未知,与其插入的顺序也可能不同
在这里插入图片描述


十二、 浅谈 list 容器

1. list 容器的概念
  • list是一种序列式容器。list容器完成的功能实际上和数据结构中的双向链表是极其相似的,list中的数据元素是通过链表指针串连成逻辑意义上的线性表,也就是list也具有链表的主要优点,即:在链表的任一位置进行元素的插入、删除操作都是快速的
  • list的实现大概是这样的:list的每个节点有三个域:前驱元素指针域、数据域和后继元素指针域。前驱元素指针域保存了前驱元素的首地址;数据域则是本节点的数据;后继元素指针域则保存了后继元素的首地址。其实,list和循环链表也有相似的地方,即:头节点的前驱元素指针域保存的是链表中尾元素的首地址,list的尾节点的后继元素指针域则保存了头节点的首地址,这样,list实际上就构成了一个双向循环链。
  • 由于list元素节点并不要求在一段连续的内存中,显然在list中是不支持快速随机存取的,因此对于迭代器,只能通过“++”或“–”操作将迭代器移动到后继/前驱节点元素处。而不能对迭代器进行+n或-n的操作,这点,是与vector等不同的地方。
2. list 容器的声明
#include 
list<int> l1;
list<int> l2(4, 100); //four ints with value 100
list<int> l3(l2.begin(), l2.end());
list<int> l4(l3); //a copy of l3
3. list 容器的使用方法
用法 作用
l.begin(), l.end() 返回 list 的首、尾迭代器
l.front(), l.back() 返回 list 的首、尾元素
l.push_front() 从 list 的头部插入
l.push_back() 从 list 的尾部插入
l.pop_front() 删除第一个元素
l.pop_back() 删除最后一个元素
l.empty() 判断list是否为空
l.clear() 清空list中的所有元素

【注意】
序列必须不为空,如果当list为空的时候调用pop_back()和pop_front()会使程序崩掉

【其他方法】

  1. insert():再指定位置插入一个或多个元素(三个重载)
    l.insert(l.begin(), 100);//在 l 的开始位置插入100
    l.insert(l.begin(), 2, 100); //在 l 的开始位置插入2个100
    l.insert(l.begin(), l2.begin(), l2.end());//在 l 的开始位置插入l2的全部元素
  2. erase():删除一个元素或一个区域的元素(两个重载)
    l.erase(l.begin()); //删除 l 的第一个元素
    l.erase(l.begin(), l.end()); //删除全部的元素
  3. resize():改变 list 的长度
    如果调用resize(n)将list的长度改为只容纳n个元素,超出的元素将被删除,如果需要扩展那么调用默认构造函数T()将元素加到list末端。如果调用resize(n,val),则扩展元素要调用构造函数T(val)函数进行元素构造,其余部分相同

十三、 浅谈 forward_list 容器

1. forward_list 容器的概念

forward_list 容器以单链表的形式存储元素。forward_list 的模板定义在头文件 #include 中。forward_list 和 list 最主要的区别是:它不能反向遍历元素;只能从头到尾遍历。

2. forward_list 容器的特性
  • 无法使用反向迭代器。只能从它得到const或non-const前向迭代器,这些迭代器都不能解引用,只能自增
  • 没有可以返回最后一个元素引用的成员函数back();只有成员函数front();
  • 因为只能通过自增前面元素的迭代器来到达序列的终点,所以push_back()、pop_back()、emplace_back()也无法使用。
  • forward_list 的操作比 list 容器还要快,而且占用的内存更少,尽管它在使用上有很多限制,但仅这一点也足以让我们满意了。
3. forward_list 容器的使用方法

3.1 求list中元素个数。

forward_list 的迭代器都是前向迭代器。它没有成员函数 size(),因此不能用一个前向迭代器减去另一个前向迭代器,但是可以通过使用定义在头文件 iterator 中的 distance() 函数来得到元素的个数。

例如:

	forward_list<string> l{"one", "two", "three"};
    auto num = distance(l.begin(), l.end());
    cout << num;  // 3

distance() 的第一个参数是一个开始迭代器,第二个参数是一个结束迭代器,它们指定了元素范围。当需要将前向迭代器移动多个位置时,advance() 就派上了用场。

例如:

    forward_list<string> l{"one", "two", "three", "one", "two", "three"};
    auto it = l.begin();
    advance(it, 4);
    cout << *it << endl; //two

[注意]

  1. advance的第二个参数不能超过list的长度,否则会出错
  2. advance() 函数会将前向迭代器自增需要的次数。这使我们不必去循环自增迭代器。需要记住的是这个函数自增的是作为第一个参数的迭代器,但是并不会返回它——advance() 的返回类型为 void

你可能感兴趣的:(STL,C++,stl,c++)