条款2:小心对“容器无关代码”的幻想

1.STL是建立在泛化之上的

数组泛化为容器,参数化了所包含的对象的类型。
函数泛化为算法,参数化了所用的迭代器的类型。
指针泛化为迭代器,参数化了所指向的对象的类型。

2.迭代器基本原理

迭代器是一个“可遍历STL容器内全部或部分元素”的对象。
迭代器指出容器中的一个特定位置。
迭代器就如同一个指针。
迭代器提供对一个容器中的对象的访问方法,并且可以定义了容器中对象的范围。
这里大概介绍一下迭代器的类别。
输入迭代器:也有叫法称之为“只读迭代器”,它从容器中读取元素,只能一次读入一个元素向前移动,只支持一遍算法,同一个输入迭代器不能两遍遍历一个序列。
输出迭代器:也有叫法称之为“只写迭代器”,它往容器中写入元素,只能一次写入一个元素向前移动,只支持一遍算法,同一个输出迭代器不能两遍遍历一个序列。
正向迭代器:组合输入迭代器和输出迭代器的功能,还可以多次解析一个迭代器指定的位置,可以对一个值进行多次读/写。
双向迭代器:组合正向迭代器的功能,还可以通过--操作符向后移动位置。
随机访问迭代器:组合双向迭代器的功能,还可以向前向后跳过任意个位置,可以直接访问容器中任何位置的元素。
²  目前本系列教程所用到的容器,都支持双向迭代器或随机访问迭代器,下面将会详细介绍这两个类别的迭代器。

 

3.双向迭代器与随机访问迭代器

双向迭代器支持的操作:
it++,  ++it,    it--,   --it,*it, itA = itB,
itA == itB,itA != itB
其中list,set,multiset,map,multimap支持双向迭代器。
随机访问迭代器支持的操作:
在双向迭代器的操作基础上添加
it+=i, it-=i, it+i(或it=it+i),it[i],
itA<itB,   itA<=itB,  itA>itB,  itA>=itB  的功能。
其中vector,deque支持随机访问迭代器。

4.Vector不能保存bool值

如果你从vector切换到其他东西,你也需要确认你不再依靠vector的C兼容的内存布局;如果你是切换到一个vector,你需要保证你不用它来保存bool。
 

5.改变容器类型方法

既然有了要一次次的改变容器类型的必然性,你可以用这个常用的方法让改变得以简化:使用封装,封装,再封装。其中一种最简单的方法是通过自由地对容器和迭代器类型使用typedef

不要这么写:

class Widget {...};
vector<Widget> vw;
Widget bestWidget;
...					// 给bestWidget一个值
vector<Widget>::iterator i =		// 寻找和bestWidget相等的Widget
	find(vw.begin(), vw.end(), bestWidget);


要这么写:

class Widget { ... };
typedef vector<Widget> WidgetContainer;
typedef WidgetContainer::iterator WCIterator;
WidgetContainer cw;
Widget bestWidget;
...
WCIterator i = find(cw.begin(), cw.end(), bestWidget);

这是改变容器类型变得容易得多,如果问题的改变是简单的加上用户的allocator时特别方便。(一个不影响对迭代器/指针/参考的失效规则的改变)

class Widget { ... };
template<typename T>					// 关于为什么这里需要一个template
SpecialAllocator { ... };					// 请参见条款10
typedef vector<Widget, SpecialAllocator<Widget> > WidgetContainer;
typedef WidgetContainer::iterator WCIterator;
WidgetContainer cw;					// 仍然能用 
Widget bestWidget;
...
WCIterator i = find(cw.begin(), cw.end(), bestWidget);	// 仍然能用

如果typedef带来的代码封装作用对你来说没有任何意义的话,你仍然会称赞它们可以节省许多工作。比如,你有一个如下类型的对象

map<string,
	vectorWidget>::iterator,
	CIStringCompare>		// CIStringCompare是“忽略大小写的字符串比较”
					// 参见条款19
而且你要用const_iterator遍历这个map,你真的想不止一次地写下

map<string, vectorWidget>::iterator, CIStringCompare>::const_iterator​

当你使用STL一段时间以后,你会认识到typedef是你的好朋友。

typedef只是其它类型的同义字,所以它提供的的封装是纯的词法(译注:不像#define是在预编译阶段替换的)。typedef并不能阻止用户使用(或依赖)任何他们不应该用的(或依赖的)。如果你不想暴露出用户对你所决定使用的容器的类型,你需要更大的火力,那就是class。

要限制如果用一个容器类型替换了另一个容器可能需要修改的代码,就需要在类中隐藏那个容器,而且要通过类的接口限制容器特殊信息可见性的数量。比如,如果你需要建立一个客户列表,请不要直接用list。取而代之的是,建立一个CustomerList类,把list隐藏在它的private区域:

class CustomerList {
private:
	typedef list<Customer> CustomerContainer;
	typedef CustomerContainer::iterator CCIterator;
	CustomerContainer customers;
public:			// 通过这个接口
	...			// 限制list特殊信息的可见性
};

一开始,这样做可能有些无聊。毕竟一个customer list是一个list,对吗?哦,可能是。稍后你可能发现从列表的中部插入和删除客户并不像你想象的那么频繁,但你真的需要快速确定客户列表顶部的20%——一个为nth_element算法量身定做的任务(参见条款31)。但nth_element需要随机访问迭代器,不能兼容list。在这种情况下,你的客户“list”可能更应该用vector或deque来实现。

当你决定作这种更改的时候,你仍然必须检查每个CustomerList的成员函数和每个友元,看看他们受影响的程度(根据性能和迭代器/指针/引用失效的情况等等),但如果你做好了对CustomerList地实现细节做好封装的话,那对CustomerList的客户的影响将会很小。你写不出容器无关性代码,但他们可能可以。





猜你喜欢

转载自blog.csdn.net/qq_29422251/article/details/78694242