工作之后更多地接触到STL,在项目中STL的使用更是屡见不鲜。最近在看此书,有必要小小地总结一下。
1.用empty()而不用size()==0去判断容器是否为空
从功能上看,两者是一样的。但性能上可能会有所差别。对于vector而言,size()其实就是end()-begin(), 因为它是连续内存分布,所以这样计算size的时间复杂度是O(1)。但对于list这类非连续内存的容器来说,只能通过遍历整个容器来获得它的size,算法复杂度为O(n)。而empty()则是通过在容器的开头记录一个flag,只要简单查询这个flag的值,就知道容器是否为空。
那么问题来了,为什么list不能让size()在O(1)内完成呢?其实是可以的。但是,list的底层结构是链表,约定俗成的习惯是链表的增删操作是O(1)的,这样size()就只能委屈一下了,变成了线性时间操作,每次增删元素,我们可能在O(1)内完成,但size()就必须遍历链表了。
另外,既然知道size()不一定是轻量级的,那么我们在写for循环的时候(尤其是遍历list),可以考虑把size_t size = list::size()这句放在循环体的外面,这样我们就不用每次迭代都去计算重量级的size()了。但其实好多人还是会写for(size_t i = 0; i < list::size(); ++i) ... 这样的代码,为什么呢?可以少写一行,增加可读性么?这种性能开销相对于可读性来说也太大了吧。
2.使用reserve来避免不必要的内存重新分配
当容器的capacity不够了,就会申请多一倍的内存,然后把数据全部拷过去。申请新内存,拷贝,释放旧内存,这三步操作是很耗时间的。所以如果我们预先能知道容器的大概capacity,就可以用reserve来预留空间,减少了内存不必要的reallocate。例如以下两段代码,性能差别很大。
vector<int> v; for (int i = 1; i <= 1000; ++i) v.push_back(i);
vector<int> v; v.reserve(1000); for (int i = 1; i <= 1000; ++i) v.push_back(i);
好像显得我太罗嗦了,这样写下去永远都写不完,明天再写,简短一点。