STL二
STL二
迭代器失效
为什么使用insert插入元素的时候迭代器可能会指向一个随机值?
因为如果插入的元素过多,那么vector就会发生扩容,然后把之前的数据复制到另一个新开辟的空间里了,然后迭代器所指的位置还是之前的位置,变成野指针了,因此它的值就可能是随机值了,并且继续插入的话,那就是在旧空间继续插入,也就是操纵野指针,是未定义行为了,可能会导致程序崩溃。
这个问题称为迭代器失效,解决办法:每次在使用迭代器之前就把迭代器重新置位一下
1
2
3 it = vec.begin();
it += 2;
//... 使用迭代器
vector的erase操作
1
2
3
4
5
6
7
8 vector<int> number = {4,4,4,4,3,4,2,1,5,6,7,4};
//删除所有等于4的元素
for(auto it = number.begin(); it != number.end(); ++it){
if(4 == *it){
number.erase(it);
}
}
//结果发现并没有把所有的四删掉为什么没有把所有的四删掉呢?
因为每次删掉元素之后,vector中所有的元素都需要往前移动,那这样就会导致一种情况,那就是如果当前位置是4,下一个位置也是4,那么删掉当前位置的元素之后,下一个位置移动到当前位置,实际上,移动到当前位置的元素仍然符合条件,应该删除,但是上面的逻辑并没有继续进行判断, 而是将迭代器向后移动了,那就把这个元素给保留下来了,所以没有把所有的四删掉
修改为:
1
2
3
4
5 for(auto it = number.begin(); it != number.end(); ++it){
while(4 == *it){
number.erase(it);
}
}
list不会出现这样的问题,因为list删除元素不会导致容器元素移动,但是list删除需要接受一下迭代器,因为不接受的话,迭代器变成野指针了
1
2
3
4
5
6
7
8 for(auto it = number.begin(); it != number.end(); ){
if(4 == *it){
it = number.erase(it);
}
else{
++it;
}
}
清空操作
1
2
3
4 number.clear();
//只是元素删除掉了,但是并没有回收空间
number.shrink_to_file();
//将多余的空间回收掉
deque是没有capacity函数的
1 deq.capacity();//error也有clear和shrink_to_file函数
1
2 deq.clear();
deq.shrink_to_file();
list没有capacity和shrink_to_file函数,主要是list也不需要这俩,因为额list没有多余的空间,并且list的size和capacity是一样的
总结:三种序列式容器都有clear函数进行清空元素,以及获取元素个数的size函数,对于vector与deque而言,还有回收多余空间的函数shrink_to_file函数,对于vector还有记录容量大小的函数capacity
swap函数
可以交换两个容器的内容,三个序列式容器都支持
1
2 num1.swap(num2);
//num1和num2的元素发生交换
resize函数
针对于vector的如果指定新的size是旧容量的一倍以上两倍一下,那么就按两倍的扩容,如果是两倍以上的话,那就是把容量扩成指定的新size
如果指定的大小小于旧容量,就会从末尾开始删元素直至与指定大小相同
该函数list vector deque都拥有
emplace_back函数
1
2 num1.emplace_back(1);
//同push_back往后面加一个元素和push_back之间的区别:
如果是自定义类型
1
2
3
4
5
6 class Point{
...
};
vec.push_back(Point(1,2));//又要构造又要拷贝
vec.emplace(1,2);//不需要Point关键字,并且不需要进行拷贝emplace的效率更高,因为少了一次拷贝构造
对于deque还有emplace_front函数
reserve函数
用来预留空间的
front/back函数
获取容器的第一个元素/最后一个元素
list的一些特殊操作
sort
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 list<int> li = {1,4,2,3};
li.sort();//默认按照从小到的顺序排列
//传入比较器
li.sort(std::less<int>());//std::less<int>()相当于创建了一个临时对象
//或者创建局部变量
std::less<int> tmp;
li.sort(tmp);
//更改比较规则
li.sort(std::greater<int>());
//需要自定义比较规则
struct CompareList{
bool operator()(const int& lhs,const int &rhs)const{
return lhs < rhs;
}
};
li.sort(CompareList());//同样也是使用无参构造传入一个临时对象std::less与std::greater是两个类型,less等价于执行了小于符号,greater等价于执行了大于符号
同样CompareList也是自定义了比较规则,这样的写法称为函数对象的写法
reverse函数
逆置函数
1 li.reverse();//将列表元素逆置
unique函数
去重函数,去掉的是去掉连续重复的元素
1 li.unique();如果想要去除所有重复的元素,使用配合sort使用
1
2 li.sort();
li.unique();
merge函数
合并两个链表
1
2
3
4
5
6
7
8
9
10
11
12 li.merge(li2);
//发现合并完成之后,只是把li2的元素加到了li的后面
//如果想让合并完成之后仍然可以保持有序
li.sort();
l2.sort();
li.merge(li2);
//发现合并完成之后也是有序的
//那让从大到小排序然后合并可以让合并完成之也是有序的吗
li.sort(greater<int>());
li2.sort(greater<int>());
li.merge(li2);//发现又乱序了,所以想要合并完之后让链表保持有序,只能是按照从小到大的顺序进行排序,然后再merg。
//并且合并完成之后,li2就变成空了
splice函数
把另一个链表的全部元素/迭代器所指的元素/迭代器范围内的元素 移动到调用该函数的链表中迭代器所指的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 list<int> li = {1,2,3,4,2,232,332,4,34};
list<int> li2 = {1123,1,432,4,2234,23,4,232,2877};
auto it = li.begin();
++it;
++it;
li.splice(it, li2);//li2的元素全部移动到了li迭代器所指的元素的前面
auto it2 = li2.begin();
li.splice(it, li2, it2);
auto it3 = li3.end();
--it3;
li.splice(it,li2,it2,it3);//是一个左闭右开的区间,it3所指的元素是取不到的问题:splice里面的第二个参数(被移动元素的链表)和调用该函数的链表可以是同一个吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 //在同一个链表中的操作
it = li.begin();
++it;
++it;
it2 = li.end();
--it2;
--it2;
--it2;
it3 = li.end();
--it3;
//搞了一个范围 it指向第三个元素 it2指向倒数第三个元素 it3指向倒数第一个元素
li.splice(it, li, it2, it3);
//这样操作的结果就是把it2, it3之间的元素移动到了it的前面,这么做是有风险的加入 it在it2 和it3之间 的话,移动就可能发生bug
一些问题
vector扩容不是会按两倍扩容吗,为什么insert插入元素的时候有时候不遵循这个规则呢?
设capacity为n ,size为m 插入元素的个数为t
情况一:t < n - m 就不会扩容
情况二:n - m < t < m,就会按照size的两倍进行扩容
情况三: n - m < t < n; t > m ,就会按照t+m进行扩容
情况四:n - m < t , t > n;也会按照t+m进行扩容
对于push_back而言,每次插入元素的个数是固定的,只要原始的空间大小不是0,那么每次按照两倍的扩容肯定是够的,但是对于insert插入而言,因为并不知道每次插入元素的个数是多少,所以不能按照某个值的两倍进行扩容
关联式容器
set和multiset
set不能出现重复值,并且会自动排序,默认是按从小到大
multiset可以有重复值,下面只写不同的,相同的不再赘述
构造函数
跟vector类似,但是少了一个count构造的,因为set没有重复值,而count构造是构建一个具有n个相同元素的容器,所以没有
1
2
3
4
5 set<int> s = {2,34,1,1,5,6};
//希望让从大到小排序
set<int, std::greater<int>> s = {2,34,1,1,5,6};multiset构造方式跟set是完全一样的,但是k值可以不唯一
查找
有两种方法:count和find
1
2
3
4
5
6
7
8 size_t cnt = s.count(34);
set<int>::iterator it = s.find(34);
if(it!= s.end()){
//存在
}else{
//不存在
}multiset查找迭代器要找的元素如果是有重复值的,那么迭代器指向的元素是指向第一个目标元素的
lower_bound和upper_bound
lower_bound返回第一个大于等于传入key的元素的迭代器
upper_bound返回第一个大于传入key的元素的迭代器
1 | multiset<int> ms = {1,1,1,1,11,2,3,3,3,3,34,5,6,7}; |
equal_range
传入一个key值,返回一个pair<iterator,iterator> 第一个迭代器指向的是大于等于给定key的迭代器,第二个迭代是指向的是大于给定key的迭代器
1
2
3
4
5
6 pair<multiset<int>::iterator,multiset<int>::iterator> ret = number.equal_range(3);
while(ret.first != res.second){
//有值 打出来
cout << *ret.first << " ";
++ret.first;
}
insert
返回值是一个pari<iterator,bool> 因为插入可能成功也可能失败,插入失败bool就为false,itertaor就为空
并且可以传一个元素,,传大括号也可以传迭代器范围也可以
1
2
3
4
5
6
7
8
9
10
11
pair<set<int>>::iterator, bool> ret = number.insert(100);
if(ret.second){
//插入成功
}else{
//插入失败
}
vector<int> vec = {1,2,3,4,5};
s.insert(vec.begin(),vec.end());
s.insert({3,4,5,325,63423,57324});multiset的insert的返回值没必要是pair类型了,因为可以有重复的值,那插入元素就一定是成功的了,pair的second一定是true了,没什么意义了,所以insert的返回值变成iterator了
emplace_hint
erase
删除一个元素或者一定范围的元素
swap
交换两个set的元素
不支持下标操作
1 | s[0];//error |
不支持修改容器内元素的值
因为底层是由红黑树实现的,所以不允许通过迭代器修改元素的值,因为修改值会改变红黑树的结构
往set中存自定义类型
对于set容器而言,如果传递的类型key是自定义类型,那么第二个模板参数Compare就需要改写,改写的方式由如下几种,要么直接改写std::less,要么给Compare重新传递一个模板参数,而std::less本质上就是在比较两个自定义类型的小于符号,所以可以有小于符号的重载;可以将std::less针对于自定义类型及进行特化;可以自己实现一个类,重载函数调用运算符(写法类似std::less),最终,set针对于自定义类型而言有三种改写方法:==模板特化,运算符重载,函数对象==
1 |
|
重载小于符号可以,但是重载大于符号行不行?
不可以,因为模板的Compare默认是less
,没有设定Compare的时候,只能重载<即小于符号
模板的特化
1 | class Point{ |
也可以自定以Compare类
1 | struct ComparePoint{ |
multiset针对与自定义类型而言,也需要改写第二个模板参数Compare,改写的方法有三种:==模板特化,运算符重载,函数对象==
map
构造函数
1.map存放的是key-value类型,key值是唯一的,不能重复,value值可以相同也可以不同
2.默认会按照key升序排列
3.底层使用红黑树实现的
1 | map<int,string> m = { |