引言
STL中为了更好的遍历、访问数据,为容器提供了迭代器。不同于for循环的遍历方式,迭代器不会暴露数据内部的联系,保证数据的安全。有了迭代器的基石,STL又为容器提供了诸多的算法,大大提高了数据的处理效率。处理数据时,迭代器的与算法的选择则会成为我们优化代码的一个基本门槛。
迭代器与算法
STL内部一共提供了四种迭代器: iterator、const_iterator、reverse_iterator、const_reverse_iterator。
迭代器的区别
默认情况下,四种迭代器都是可以选择的,它们的直观的区别在于其功能与作用:
- iterator 作用是常规迭代器,功能相当于T*
- const_iterator 作用是常量类型迭代器,功能相当于const T*
- reverse_iterator 作用是反向迭代器,功能相当于T*
- const_reverse_iterator 作用是反向常量迭代器,功能相当于const T*
它们之间有着明确的转化关系,iterator可以隐式转化为const_iterator或reverse_iterator迭代器。const_iterator与reverse_iteraotr,可以转化为const_reverse_iterator迭代器。reverse_iterator可通过base()转化为iterator,const_reverse_iterator可以通过base()转化为const_iterator。
这里通过base()进行的迭代类型的转换,最后的结果可能并非是我们想要得到的内容。由于base()与隐式转换可能导致的一些潜在的问题,为了避免最好使用默认的iterator迭代器。
const_iterator转化iterator
- 创建一个iterator迭代器
- 将iterator指针指向 const_iterator初始位置
算法中的区间目标
当新对象添加进来时,总是认为STL会为数据准备好了空间,可以直接使用而不关心。但事实上并非如此,如下例所示:
1 | int transmogrify(int x) // 生成新值 |
这样有一个问题,即res.end()本身是没有对象,那么开辟*(res.end())+1同样也是没有对象的。显而易见,这会向一个不存在的地址写入数据,而这样会导致程序出错。
STL提供了back_inserter来处理,其实就是调用back_push来插入数据,这样在扩展数据容量的同时,也保证了数据写入的安全性。当然,如果有头部插入的需求,我们可以使用front_inserter来处理。
当然,你考虑到vector扩容所带来的影响,你也可以使用reserve来对vector的capacity进行预扩展,记住这是capacity,不是真正的size,若是使用transfrom安全的方式插入仍会导致程序错误。
算法之排序
若是提到排序,STL中的sort必然是首推,但是并非所有情况下的它都是最优的选择。如下面各种不同的场景。
- 有堆刚刚到货物,仅需要排序出前十个最好货物时,可以使用parital_party来进行处理(部分排序)。
- 货物到货之后,销售人员告知不需要排序,仅仅需要将找到的十个最好的给她即可,即可使用nlt_element(区间排序)
这两个排序算法: parital_party与nlt_element都是非稳定的排序算法。非稳定性即,在遇到两个等价的值时,排序算法会将两个数据进行交换。而需要稳定性算法时,我们可以使用STL提供的stale_sort来处理。
若有对将特定元素放到元素前部的要求时,我们可以使用parition算法。当然对于不稳定的它,STL也提供了stable_parition。
要是没有稳定性的考虑的话,效率优先最好不选用stable算法。
remove的实质
remove是无法做到删除的,它操作的结果是覆盖。若是真正的需要删除,就请使用erase_remove手法进行删除。
若是遇到一些对象是指针的容器,我们需要小心处理,因为erase会删除容器中的指针,但并不会删除指针所指向的对象,这就会导致资源的泄漏。
为也避免,我们需要在使用erase(remove)手法删除前,对数据进行验证并将其置空,之后通过erase(remove)手法清除空指针。
这样处理资源的方式,终就会存在一定的风险,若是想要安全保险,最好还是使用智能指针来管理你的容器对象。
结语
迭代器根据作用有不同的适用场景,而安全又保险的使用则是默认的iterator迭代器。算法有很多,但是不要认为STL会为我们提供好一切,它仅提供操作,内部的资源管理我们仍需要小心。