一则故事
有一天我准备集成一个同事的代码,在我简单测试的时候报错了
当我定位到代码位置时,发现他在访问list容器最后一个元素时,直接auto iter = list_.end();
当我去和他讨论这个问题时,我和他说,一般stl容器end()指的是容器最后一个元素的下一个位置。而他在网上找到的资料却是,end()指的是访问容器最后一个元素。之后他发现的原因是vscode自动帮他优化了。
这里我还是建议看看标准的文档
std::list<T,Allocator>::end, std::list<T,Allocator>::cend - cppreference.com
STL容器的逻辑设计上的结构与数学上的“前闭后开”区间是差不多的,
就比如想表示某容器有0,1,2,3,那么容器就得表示为[0,4),即0为begin(),4就是end(),因此,end()必须是指向容器实际最后一个元素的下一个位置
而每个容器都会“有头有尾”,即begin()和end(),均为迭代器(我们可以认为是个头指针和尾指针来表示区间的左右边)
为什么是“前闭后开”?如图对比可看出,
- “前闭后闭”的话,不管是空容器还是容器有单个元素时,需要增加额外的判断才能区分出来,容易存在边界问题
- “前闭后开”的话,只要这两个指针差1,就能表示有单个元素;这两个指针相等就能表示为空容器。
STL容器
我在听侯捷老师的课的时候总结了几个关于各大容器的底层结构:
顺序容器
顺序容器它并不是物理上的内存连续,而是逻辑上的连续,也就是我们可以根据容器中的位置来存放和访问元素,比如某容器A,我可以通过A[0]访问到第0位置的元素
其中顺序容器有array、vector、deque、list、forward-list ...
array --- 固定大小数组
顾名思义,它的大小是固定的
也就是,比如 std::array<int,3> a = {0,1,2}; 想让a插入一个4,是不可能的,它的大小只有3,仅能修改容器里面的元素值
vector---可变数组(向量)
与array相反,它的容量大小是可以变化的,比如我们通过push_back或emplace_back插入元素时,vector的size()增加!
注意,vector的实际空间大小capatity()会比size()大!
- vector的扩充是2倍增长的,实际上的2倍增长是找更大的空间将原来容器里面的元素搬到过去,并非在模为扩充。
- vector新增元素是将元素push到容器的尾部,但有个前提,vector有足够的空间push。
- 假设一个房间可以装20个人,目前已经有20个人,准备插入一个人的时候,插不进了,这时候将这20个人移到可以装下40人的房间里面去,这样的话,之后很多次push的时候不需要去走申请内存的流程了。
deque---双端队列
两端都均可以扩充,均可以push
- deque是分段连续的,由多个buffer组合而成,但逻辑上是整段连续的,即仍可以通过下标来访问元素
- 它的扩充则就是可以在两端新增多个buffer
- deque可以实现stack栈(“先进后出”),也可以实现queue(“先进先出”)
list---双向链表
动态分配内存
forward-list---单向链表
动态分配内存
关联容器
说到"关联"两个字,我们应该能联想到数据库,关联容器也相当于一个小型关联数据库,有唯一的key对应其value,因此,很便于查找
下面的容器,若只要带有multi,也就是key可以重复
有序容器(本质是红黑树)
有序指的是key是有顺序的,
set/multiset
- key = value,比如 std::set<int> _s = {2,4,5};其中key=2的value=2
map/multimap
- 其中multimap无法通过中括号来插入,比如std::multimap<int,int> _map = {{2,1},{2,3}},若我_map[2] = 4,你说我到底要改哪个key的值?
无序容器(本质是链式哈希表)
其中有unordered_set/unordered_multiset,unordered_map/unordered_multimap
哈希表中的篮子即bucket一定会比实际元素多,当元素个数大于等于篮子个数时,哈希表会扩充到原来的两倍,再对所有元素重新哈希