我们知道,一个数组必须要有固定的长度,在开一个数组的时候,这个长度也就被静态地确定下来了。但是vector却是数组的“加强版”,对于一组数据来讲,你往vector里存多少数据,vector的长度就有多大。也就是说,我们可以将其理解为一个“变长数组”。
事实上,vector的实现方式是基于倍增思想的:假如vector的实际长度为n,m为vector当前的最大长度,那么在加入一个元素的时候,先看一下,假如当前的n=m,则再动态申请一个2m大小的内存。反之,在删除的时候,如果n<=m/2,则再释放一半的内存。
vector容器存放在模板库:
#include
里,使用前需要先开这个库。
容器类型<变量类型> 名称
例如:
#include
vector<int> vec;
vector<char> vec;
vector<pair<int,int> > vec;
vector<node> vec;
struct node{...};
用法 | 作用 |
---|---|
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 在英文中是队列的意思。队列是一种基本的数据结构。而C++STL中的队列就是把这种数据结构模板化了。我们可以在脑中想象买票时人们站的排队队列。我们发现,在一个队列中,只可以从队首离开,从队尾进来(没有插队,想啥呢)。即一个先进先出的数据结构。
queue容器存放在模板库:
#include
里,使用前需要先开这个库。
容器类型<变量类型> 名称
例:
#include
queue<int> q;
queue<char> q;
queue<pair<int,int> > q;
queue<node> q;
struct node{...};
用法 | 作用 |
---|---|
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 在英文中是栈的意思。栈是一种基本的数据结构。而C++STL中的栈就是把这种数据结构模板化了。
栈的示意图如下:这是一个先进后出的数据结构。这非常重要!!
事实上,stack容器并不是一种标准的数据结构,它其实是一个容器适配器,里面还可以存其他的STL容器。但那种使用方法过于高深而且不是很常用,所以在此不与介绍。请有兴趣的读者自行查询资料。
stack容器存放在模板库:
#include
里,使用前需要先开这个库。
容器类型<变量类型> 名称
例:
#include
stack<int> st;
stack<char> st;
stack<pair<int,int> > st;
stack<node> st;
struct node{...};
用法 | 作用 |
---|---|
st.top() |
返回stack的栈顶元素 |
st.push() |
从stack栈顶加入一个元素 |
st.size() |
返回stack当前的长度(大小) |
st.pop() |
从stack栈顶弹出一个元素 |
st.empty() |
返回stack是否为空,1为空、0不为空 |
**【注意】**不要把queue的获取队头元素和stack的获取栈顶元素两个方法混淆
其实string并不是STL的一种容器,但是由于它的使用方法等等和STL容器很像,所以就把它当作STL容器一样介绍。
其实string容器就是个字符串,这通过它的英文译名就能看得出来。但是对于字符串以及字符串的相关操作,可能读者还是对普通的C/C++的#include,#include库更熟悉一些。我丝毫不否认这些传统字符操作的经典性和实用性,但是由于它们函数定义的局限,有些时候对于一些特殊的读入、输出、遍历等要求,它的操作并不如string容器好用。
比如,要求读入一群中间可能带空格的字符串,如果用传统方式进行读入,可能就会很麻烦,但是如果使用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 容器存放在模板库:
#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)【小根堆声明方式】
- 大根堆是把大的元素放堆顶,小根堆就是把小的元素放到堆顶。
- 实现小根堆有两种方式:
- 因为优先队列默认实现的是大根堆,所以我们可以把元素取反放进去,因为负数的绝对值越小越大,那么绝对值较小的元素就会放在前面,我们在取出的时候再取个反,就瞒天过海地用大根堆实现了小根堆。
- 小根堆有自己的声明方式,但是比较复杂
priority_queue
, greater > q;
【注意】当我们声明的时候碰到两个"<“或者”>"放在一起的时候,一定要记得在中间加一个空格。这样编译器才不会把两个连在一起的符号判断成位运算的左移/右移。
用法 | 作用 |
---|---|
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的意义是:双端队列。队列是我们常用而且必须需要掌握的数据结构。C++STL中的确有模拟队列的模板:#include中的queue和priority_queue。队列的性质是先进先出,即从队尾入队,从队首出队。而deque的特点则是双端进出,即处于双端队列中的元素既可以从队首进/出队,也可以从队尾进/出队。
- 即:deque是一个支持在两端高效插入、删除元素的线性容器。
- deque模板存储在C++STL的
#include
中。
#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) );
用法 | 作用 |
---|---|
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在英文中的意义是:集合。set容器也的确“人如其名”,实现了这个集合的功用。
- 高中数学必修一集合那章(高一以下的小伙伴不用慌,不讲数学只讲概念),关于集合的性质,给出了三个概念:无序性、互异性、确定性。
- 那么,set容器的功用就是维护一个集合,其中的元素满足互异性。
- 我们可以将其理解为一个数组。这个数组的元素是两两不同的。
- 这个两两不同是指,如果这个set容器中已经包含了一个元素i,那么无论我们后续再往里假如多少个i,这个set中还是只有一个元素i,而不会出现一堆i的情况。这就为我们提供了很多方便。
- 但是,需要额外说明的是,刚刚说集合是有无序性的,但是set中的元素是默认排好序(按升序排列)的。(稍微说一句,set容器自动有序和快速添加、删除的性质是由其内部实现:红黑树(平衡树的一种)。这个东西过于高深我不会,所以不予过多介绍,有兴趣的小伙伴可以自行浏览相关内容。)
set容器的声明和大部分C++STL容器一样,都是:容器名<变量类型> 名称的结构。前提需要开#include库。如:
#include
set<int> s;
set<char> s;
set<pair<int, int> > s;
set<node> s;
struct node{...};
用法 | 作用 |
---|---|
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() |
清空当前集合。 |
- set在英文中的意义是:集合。而multi−前缀则表示:多重的。所以multiset容器就叫做:有序多重集合。
- multiset 的很多性质和使用方式和set容器差不了多少。而multiset容器在概念上与set容器不同的地方就是:set的元素互不相同,而multiset的元素可以允许相同。
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独有的。
// 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 容器其实就是个01串。可以被看作是一个bool数组。它比bool数组更优秀的优点是:节约空间,节约时间,支持基本的位运算。在bitset容器中,8位占一个字节,相比于bool数组4位一个字节的空间利用率要高很多。同时,n位的bitset在执行一次位运算的复杂度可以被看作是n/32,这都是bool数组所没有的优秀性质。
因为bitset容器就是装01串的,所以不用在< >中装数据类型,这和一般的STL容器不太一样。< >中装01串的位数。
如:(声明一个10^5 位的 bitset)
bitset<100000> s;
用法 | 作用 |
---|---|
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不为空 |
bitset的作用就是帮助我们方便地实现位运算的相关操作。它当然支持位运算的一些操作内容。我们在编写程序的时候对数进行的二进制运算均可以用在bitset函数上。
符号 | 作用 |
---|---|
~ |
按位取反 |
& |
按位与 |
| |
按位或 |
^ |
按位异或 |
<< | >> |
左移 / 右移 |
== | != |
比较两个bitset是否相等 |
[补充]
bitset容器还支持直接取值和直接赋值的操作:具体操作方式如下:
s[3]=1; s[5]=0;
[注意]
在bitset容器中,最低位为0。这与我们的数组实现仍然有区别。
bitset可以高效率地对01串,01矩阵等等只含0/1的题目进行处理。其中支持的许多操作对我们处理数据非常有帮助。如果碰到一道0/1题,使用bitset或许是不错的选择。
map 的英语释义是“地图”,但map容器可和地图没什么关系。map是“映射容器”,其存储的两个变量构成了一个键值到元素的映射关系。
比如下图:
我们可以根据键值快速地找到这个映射出的数据。
map 容器的内部实现是一棵红黑树(平衡树的一种),因为比较复杂而且与理解并无多大关系,所以不予介绍,有兴趣的读者可以自己查阅相关的资料。
map容器存在于STL模板库
#include
中。使用的时候需要先开这个库。
#include
map
mp;
这个mp可用来统计每个字符出现的次数
因为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中的元素 |
【其他】
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;
}
- 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;
}
我们发现,map和C++内置二元组pair特别相似。那是不是map就是pair呢?(当然不是)
首先,map构建的关系是映射,也就是说,如果我们想查询一个键值,那么只会返回唯一的一个对应值。但是如果使用pair的话,不仅不支持O(log)级别的查找,也不支持知一求一,因为pair的第一维可以有很多一样的,也就是说,可能会造成一个键值对应n多个对应值的情况。这显然不符合映射的概念。
map: #include < map >
unordered_map: #include < unordered_map >
- map:map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。
- unordered_map:unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。
[map]
- 优点:有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作。红黑树,内部实现一个红黑树使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率非常的高
- 缺点:空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间
- 适用点:对于那些有顺序要求的问题,用map会更高效一些
【unordered_map】
- 优点:因为内部实现了哈希表,因此其查找速度非常的快
- 缺点:哈希表的建立比较耗费时间
- 适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map
[总结]
- 内存占有率的问题就转化成红黑树 VS hash表 , 还是unordered_map占用的内存要高。但是unordered_map执行效率要比map高很多。
- 对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的
- unordered_map的用法和map是一样的,提供了 insert,size,count等操作,并且里面的元素也是以pair类型来存贮的。其底层实现是完全不同的,上方已经解释了,但是就外部使用来说却是一致的。
#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;
}
【运行结果】
【若将 unordered_map 改为 map,结果如下】
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是一种序列式容器。list容器完成的功能实际上和数据结构中的双向链表是极其相似的,list中的数据元素是通过链表指针串连成逻辑意义上的线性表,也就是list也具有链表的主要优点,即:在链表的任一位置进行元素的插入、删除操作都是快速的
- list的实现大概是这样的:list的每个节点有三个域:前驱元素指针域、数据域和后继元素指针域。前驱元素指针域保存了前驱元素的首地址;数据域则是本节点的数据;后继元素指针域则保存了后继元素的首地址。其实,list和循环链表也有相似的地方,即:头节点的前驱元素指针域保存的是链表中尾元素的首地址,list的尾节点的后继元素指针域则保存了头节点的首地址,这样,list实际上就构成了一个双向循环链。
- 由于list元素节点并不要求在一段连续的内存中,显然在list中是不支持快速随机存取的,因此对于迭代器,只能通过“++”或“–”操作将迭代器移动到后继/前驱节点元素处。而不能对迭代器进行+n或-n的操作,这点,是与vector等不同的地方。
#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
用法 | 作用 |
---|---|
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()会使程序崩掉
【其他方法】
- 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的全部元素- erase():删除一个元素或一个区域的元素(两个重载)
l.erase(l.begin());
//删除 l 的第一个元素
l.erase(l.begin(), l.end());
//删除全部的元素- resize():改变 list 的长度
如果调用resize(n)
将list的长度改为只容纳n个元素,超出的元素将被删除,如果需要扩展那么调用默认构造函数T()将元素加到list末端。如果调用resize(n,val)
,则扩展元素要调用构造函数T(val)函数进行元素构造,其余部分相同
forward_list 容器以单链表的形式存储元素。forward_list 的模板定义在头文件 #include
中。forward_list 和 list 最主要的区别是:它不能反向遍历元素;只能从头到尾遍历。
- 无法使用反向迭代器。只能从它得到const或non-const前向迭代器,这些迭代器都不能解引用,只能自增;
- 没有可以返回最后一个元素引用的成员函数back();只有成员函数front();
- 因为只能通过自增前面元素的迭代器来到达序列的终点,所以push_back()、pop_back()、emplace_back()也无法使用。
- forward_list 的操作比 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
[注意]:
- advance的第二个参数不能超过list的长度,否则会出错
- advance() 函数会将前向迭代器自增需要的次数。这使我们不必去循环自增迭代器。需要记住的是这个函数自增的是作为第一个参数的迭代器,但是并不会返回它——advance() 的返回类型为 void。