第一章 容器
第2条:不要试图编写独立于容器类型的代码
首先作者提出,不能尝试泛化出一个适用于所有容器的通用容器。要承认不同容器的不同。容器不是被设计用来交换使用的。然后,作者提出,实际中,我们肯定会遇到更改已经在大量代码中存在的容器。可以使用封装技术。 书中列出了两种方法:
(1)使用类型定义typedef
class Widget{};
typdef vector<Widget> W;
typedef W::iterator QW;
W cw;
Widget dd;
QW i=find(cw.begin(),cw.end(),dd);
如果更改vector类型,只需要更改一处。
(2)使用类封装
类型定义不能更改用户的使用权限。如果不想把这部分暴露给用户,就应该使用类。。可以将容器隐藏在一个类中。书中定义了一个类。类中包含一个list类型别名的成员和它的迭代器成员。但是当要使用nth_element时,需要将list改成vector或deque,这样的操作很方便,不会影响被封装在外的用户。但是还需要检查成员函数和友元函数,有没有要更改的内容。
第3条:确保容器中的对象拷贝正确而高效
原因:
(1)STL的插入、取出等操作时采用的复制机制。复制机制需要用到复制构造函数和赋值运算符重载。stl的复制机制是影响其性能的重要因素;
(2)在存在继承机制的关系时,拷贝动作会导致剥离。
措施:
不是建立对象的容器,而是建立对象指针的容器。
第4条:调用empty而不是检查size()是否为0
c.size()==0和c.empty()本质上是等价的,但是推荐使用empty()判断容器是否为空。原因是,对于任何容器,empty()的复杂度都是常数时间,但是对于list的size操作,可能是线性时间。因为list是链式存储的。考虑下面的例子:
list<int> list1;
list<int> list2;
...
list1.splice( // 把list2中
list1.end(), list2, // 从第一次出现5到
find(list2.begin(), list2.end(), 5), // 最后一次出现10
find(list2.rbegin(), list2.rend(), 10).base() // 的所有节点移到list1的结尾。
); // 关于调用的
// "base()"的信息,请参见条款28
splice的时间复杂度取决于list的设置。这个函数的基本功能是将另外一个链表序列接到当前序列的尾部,如果要知道现在序列的长度,需要遍历一遍(线性时间)链表。
如果list的设置是:size()为常数时间,那么splice操作在完成基本功能的同时还要做线性时间的遍历操作。如果list的size()是线性时间,那么splice函数不用承担这个责任。
不管如何,size()的复杂度可能是线性的或常数的,empty一定是常数时间。