Effective STL笔记(一)

来源:互联网 发布:季汉 知乎 编辑:程序博客网 时间:2024/05/21 10:52

1慎重选择容器类型

 

序列容器:

vector, string, deque, list

关联容器:

set, multiset, map, multimap

非标准序列容器:

hash_set, hash_multiset, hash_map, hash_multimap

非标准STL容器:

数组, bitset, valarray, stack, queue, priority_queue


2不要试图编写独立于容器类型的代码

 

不要想着对容器的概念泛化。比如使用vector,但仍保留以后将其换成dequelist的选择,而不必改变使用该容器的代码。如果这样做,则该程序只能使用这些容器的功能的交集。如reservecapacity只在vector中有,dequelist中不存在这样的操作。进一步,还得把操作限制在双向迭代器的能力范围,而sortstable_sortpartial_sortnth_element都需要随机访问迭代器。

 

不同容器是不同的。它们有非常明显的优缺点。它们并不是被设计来交换使用的。(所以,需要“慎重选择容器类型”!!!)

 

若考虑以后可能会不可避免的从一种容器类型转换到另一种,可以使用“封装(encapsulation)技术”。最简单的方式是通过对容器类型和其迭代器类型使用类型定义(typedef)。(有用,但在此效果不大。)

 

另外一种方法是使用“类(class)”。并尽量减少那些通过类接口(而使外部)可见的、与容器相关的信息。


3 确保容器中的对象拷贝正确而高效

 

向容器中加入对象时,存入容器的是该对象的拷贝(按位拷贝或用户自定义的拷贝)。这将带来两个问题。

一、拷贝操作可能很费时,向容器填充对象这一简单操作可能成为性能瓶颈

二、存在继承关系时,拷贝操作可能会发生剥离(slicing)。一个存放基类对象的类,却向其插入派生类的对象,则派生类对象被拷贝进容器后,它所特有的部分(派生类中的信息)将会丢失。

 

解决上两个问题的一个简单操作是使容器包含指针而不是对象。拷贝指针的速度快,并且不会有剥离现象发生(多态)。第7条和第33条描述了使用指针容器的一些问题。使用智能指针smart pointer)是一个诱人的选择。



第4条 调用empty而不是检查size()是否为0

 

empty操作对所有的标准容器都是常数时间操作,而对于一些list的具体实现,size()可能是线性时间的操作。

原因在于list的splice()操作。该操作会影响list对象的元素个数。

list<int>list1;list<int>list2;…list1.splice(list1.end(),list2,                    find(list2.begin(),list2.end(), 5),                    list2.end());

 

这段操作将list2中的一段区间移动到list1中。该操作过后,list1和list2中的元素个数都将改变,那选择在这时候更新list中的元素个数的记录(遍历两个list),使该splice()操作成为线性时间的操作;还是选择在需要获取list元素个数时才遍历list,而使splice()成为常数时间的操作?这是个tradeoff!!!(sgi选择的是使size()遍历list,保持splice()操作的常数时间)


第5条 区间成员函数优先于与之对应的单元素成员函数

 

两个vector v1和v2,将v2中的元素附加到v1的尾部,是一个一个的加入

for(it =v2.begin(); it != v2.end(); ++it){    v1.push_back(*it);}

还是使用

v1.insert(v1.end(),v2.begin(), v2.end());

 

  1. 使用区间成员函数,通常可以少写一写代码
  1. 使用区间成员函数,通常会得到意图清晰和更加直接的代码。

 

在效率上,使用单元素的成员函数比使用区间成员函数(可能)需要更多地调用内存分配子,更频繁的拷贝对象,而且/或者做冗余的操作。

 

另外,如果想把v2中的元素按原顺序插入到v1的头部的话,使用for循环需要这样

for(it =v2.rbegin(); it != v2.rend(); ++it){    v1.push_front(*it);//push_front操作完全没效率    //或者 v1.insert(v1.begin(), it.base()-1);}

 

v1.insert(v1.begin(),v2.begin(), v2.end()); 

可以一次性分配需要的内存,避免一次又一次的重复分配内存。

 

对区间操作函数总结如下

  1. 区间创建:所有标准容器都提供了如下形式的构造函数

container::container(InputIterator begin,                                     InputIterator end);

  1. 区间插入:所有标准序列容器都提供了如下形式的insert:

void container::insert(iterator position,                                       InputIterator begin,                                       InputIterator end);

关联容器利用比较函数决定元素插入何处,所以:

void container::insert(InputIterator begin,                                       InputIterator end);

省去了position参数。

  1. 区间删除:所有标准容器都提供了区间形式的删除(erase)操作。对于序列容器,

iterator container::erase(iterator begin, iterator end);

而关联容器则是这样:

void container::erase(iterator begin, iterator end);

返回值不同。这是因为据说对于关联容器,返回删除元素之后的元素的迭代器将导致不可接受的性能负担。

  1. 区间赋值:所有标准容器都提供了区间形式的assign:

void container::assign(InputIterator begin, InputIterator end);