STL缺点:不能保证线程安全;没有树结构或是图结构;
STL的顺序容器:
1.vector 动态数组(任意时间的快速访问)#include<vector>
vector的创建:
vector<int> intVector1;//创建0个元素的vector
vector<int> intVector2(10,100);//创建10个元素的vector,每个元素值为100
vector<int> intVector3({1,2,3,4,5,6});//利用初始元素的列表初始化vector
vector<int> intVector4={1,2,3,4,5,6};
vector<int> intVector5{1,2,3,4,5,6};
(1)at()和operator[]区别:at()会执行边界检查,超出边界会返回out_of_range异常,operator[]越界会返回未定义结果;
(2)assign方法
intVector2.assign(5,10);//删除原有的所有元素,用新的元素进行替换
cout<<intVector2.at(3)<<endl;
intVector3.assign({6,5,4,3,2,1});
cout<<intVector3.at(3);
(3)swap方法
intVector3.swap(intVector4);//交换两个vector的内容
(4)循环访问元素
//使用自动匹配进行循环
for(auto element : intVector3)
{
cout<<element;
}
//使用迭代器
for(vector<int>::iterator iter = intVector3.begin();iter != intVector3.end();++iter)
{
cout<<(*iter);
}
(5)普通的iterator支持读写,但是如果对const对象调用begin()和end(),会得到const_iterator,则为只读;iterator可以转换成const_iterator;如果要结合auto使用只读的const_iterator,可以使用cbegin()和cend();
(6)end()返回vector最后元素的后一个元素;
(7)使用迭代器的好处:可以在任意位置插入、删除元素或者元素序列;方便使用STL算法;效率高;
(8)back()返回最后一个元素,pop_back()移除最后一个元素,但是没有返回值;
(9)insert()方法可以在任意位置插入元素,后面的元素依次后移;常用的三种方式:
//insert的用法,原始:123456
intVector3.insert(intVector3.begin()+3,3);//1233456
intVector3.insert(intVector3.begin()+2,3,2);//1222233456,将2插入三次
//将后两个迭代器之间的值插入第一个迭代器的位置,可用于两个vector连接
intVector3.insert(intVector3.end(),intVector4.begin(),intVector4.end());
(10)erase()方法可用于在任意位置删除元素,两种形式:单个迭代器删除单个元素;两个迭代器删除元素范围;
clear()方法可以删除一个vector的全部元素;
(11)关于vector的内存
vector会自动分配内存,内部实现机制:当一个vector当前内存不够时,会分配比所需内存更多的内存,这样就避免了每次都要复制转移;大部分操作都是常量时间,但是也有线性时间的情况;每次重分配之后,迭代器会失效,因为迭代器不会自动移动。
(12)size()方法返回当前元素个数(大小),capacity()返回重分配之前可以保存的元素个数(容量);所以还能插入的个数为capacity()-size(),多于这个个数之后就会引起内存的重分配;reserve()可以进行内存管理
empty()方法查看容器是否为空;
(13)vector<bool>,需要动态大小的位字段的时候使用;如果不需要动态,bitset更好;
2.list 双向链表(查找慢,插入删除快)#include<list>
(1)没有诸如operator[]等随机访问操作;访问元素的方法仅有front()和back();其他元素访问必须通过迭代器完成;
(2)list迭代器是双向的,迭代器只能++,或者--进行遍历,不允许使用加减符号;
(3)list的一些特殊的方法:remove()删除特定元素;unique()删除连续的重复元素;merge()合并两个list;
sort()对元素执行稳定的排序;reverse()翻转list中的元素顺序;
3.forward_list 单向链表 #include<forward_list>
因为单向,所以只支持前向迭代;提供before_begin()方法,返回指向开头元素前一个的假想迭代器(不能解开,因为指向非法数据);
由于单链表插入删除操作会改变当前数据的前一个数据,所以提供了自己的:insert_after、emplace_after、erase_after操作。
4.deque 双头队列(快速访问,两端快速插入删除,中间插入删除慢)#include<deque>
deque动态地以分段连续空间组合而成,随时可以增加一段新的连续空间并链接起来。不提供空间保留功能。
和vector相似,多了push_front(),pop_front(),emplace_front()方法;
5.array C中数组的替代品(适用于大小固定的集合,不会转化成指针)#include<array>
不支持push_back(),pop_back(),insert()...方法,因为大小固定;
(1)可以试用resize()改变容器大小(array除外)
用法:list<int> ilist(10, 42);
ilist.resize(15);//末尾添加五个0;
ilist.resize(25, -1); //末尾添加10个-1;
ilist.resize(20);//末尾的5个-1会被删除
(2)向容器中添加或者删除操作,可能会使指向容器元素的引用、指针和迭代器失效,需要注意。
(3)vector 和 string 的容量增长(因为需要满足连续空间存储)
增长方式:当前元素数量等于容量时,再向其中添加元素,会引起容量的增长,会在内存中分配比需求更大的一块内存区间,然后将之前的数据拷贝到新的内存当中。(这样的分配方式会减少数据拷贝调度次数)
capacity和reserve方法:
c.capacity()返回当前容器的容量。(与size()区分开)
c.reserve(n),会分配至少能容纳n个元素的内存空间(可能会大于n个)
ps:vector和string的内存回收
在析构的时候一次性回收内存(所以说vector的内存只会增长,不会减少)
手动释放的技巧:将一个vector与一个空的vector进行swao操作。
(4)比较常用的string方法
①构造
复制构造:string s2(s1);
直接构造:string s = "hello";
string s("hello");
容器方法:string s(n,'c');
字符数组:string s(cp,n);//前n个字符
利用其他string对象:string s(s2,pos2,len2);//拷贝s2的pos2开始的len2个字符,len2不选则拷贝到最后
②子串
s.substr(pos, n);//返回s中pos开始的n个字符的拷贝新字符串
③string除了支持迭代器的insert和erase方法之外,还提供了下标的insert和erase方法
s.insert(s.size(), 5, 'c');//在s的末尾插入5个'c'
s.erase(s.size() - 5, 5);//删除s最后的5个字符
还支持将char*和string插入到string当中。
④append方法
将char*或者是string对象或者是指定个数字符...添加到某一字符串后面
⑤replace方法
替代某一范围内的字符
⑥find函数—string查找:找到返回字符出现下标(string::size_type类型,最好不要用int保存。可以用auto),没找到返回npos(初始值为-1)
重载类型:
rfind:查找最后一次
find_first_of(args):查找args中任何一个字符第一次出现的位置
find_last_of(args);
find_first_not_of(args);查找第一个不在args中的字符
find_last_not_of(args);
查找时,还可以指定在哪个位置开始执行查找。
⑦compare函数(类似于c中的strcmp)
⑧转换
to_string(val)函数:将val数值转换成string类型
stoi,stol...将string转换成相应的类型
⑨string的运算符重载
有的时候面试中要手写string类,这个时候就要写运算符重载,有以下几种:
myString& operator=(const myString& other);//都要判断other == this
myString& operator+(const myString& other);
bool operator==(const myString& other);
char& operator[](int i);
具体可以查看:如何定义自己的string类
STL容器适配器:对顺序容器的包装,简化
适配器没有迭代器,也没有同时插入、删除多个元素的能力
6.queue 队列#include<queue>
push(),emplace(),pop(),front(),back(),size(),empty(),swap()8种方法
7.priority_queue 带有优先级的队列 #include<queue>
8.stack 栈#include<stack>
关联容器(map+set):无线性方式保存数据,将键映射到值上
四种有序关联容器(使用比较运算符组织元素):
map,set,multimap,multiset(加了multi表示该容器的关键字可以重复出现)
四种无序关联容器(使用hash函数组织):
unordered_map,unordered_set,unordered_multimap,unordered_multiset
<utility>中的pair类将两个值组合起来,通过first和second来访问这两个值:
pair<string,int> myPair("hello",5);
9.map 保存键值对,插入、查找删除都是基于键的#include<map>
map以RB-TREE(红黑树)为底层机制。
初始化:
map<string,int> m={
{"a",1},
{"b",2},
{"c",3}
};
(1)插入元素:(1)使用insert;(2)使用operator[],如果给出的键已存在,则替换。
insert(pair<>)的方式效率更高,因为使用[]运算符插入的时候:
enumMap[“a”] = "2";
.....
这样非常直观,但存在一个性能的问题。插入“a”时,先在enumMap中查找主键为“a”的项,没发现,然后将一个新的对象插入enumMap,键是“a”,值是一个int,插入完成后,将int赋为2; 该方法会将每个值都赋为缺省值,然后再赋为显示的值,如果元素是类对象,则开销比较大。
(2)也有迭代器,只不过引用的是键值对;
(3)查找元素:operator[]只能查找已存在,如果元素不存在,就会插入一个新元素,而不是查找失败;
可以使用find()方法;成功返回制定元素的迭代器,不成功则返回end()迭代器;
(4)删除元素:map.erase(key);
10.multimap 允许多个元素使用同一个键
不提供operator[];插入操作也总会成功;
lower_bound()和upper_bound()方法分别返回满足给定键值的第一个元素和最后一个元素的后一个元素对应的迭代器;如果没有元素匹配,两个方法返回的迭代器相等;
11.set 保存的不是键值对,值本身就是键#include<set>
set底层也是以红黑树的形式实现。
12.bitset #include<bitset>
默认构造函数将其字段初始化为全0:bitset<10> myBitset;
set(),reset(),flip()方法改变单个位的值;
支持&,|,^,~等运算;
13.hash_map和map的区别
(1)底层结构:hash_map基于哈希表,map基于红黑树
(2)map可以按照键值自动进行排序,hash_map操作基于常数时间
(3)map是STL的一部分,而hash_map不是
各种容器的底层实现方式:
1.vector数组实现
2.list双向链表实现
3.deque一个中央控制器+多个缓冲区(堆)(支持首尾增删和随机访问,vector和list结合品)
分段数组的首地址被连续存放在索引数组中,因此可以对其进行随机访问,但效率比vector低很多
两端插入删除比较快,但是越靠近中间插入删除越慢。
deque的内存重分配优于vector,因为其内部结构显示不需要复制所有元素。
4.stack list或者deque实现,不用vector是因为容量大小有限制,扩容耗时
5.queue list或者deque实现
6.set红黑树,有序不重复
7.multiset红黑树,有序可重复
8.map红黑树,有序不重复
9.multimap 红黑树,有序,可重复
10.hash_set hash表,无序,不重复
11.hash_multiset hash表,无序,可重复
12.hash_map hash表,无序,不重复
13.hash_multimap hash表,无序,可重复
迭代器失效
(1)vector增加元素可能导致vector内存发生转移,此时可能导致所有的迭代器失效。
(2)push_back()之后,end()失效
(3)删除一个元素之后,指向删除点和其后面的所有点的迭代器失效。