3 关联容器
~~~~~~~~~~~
3.1 了解相等和等价的区别
=========================
1. find对“相同”的定义是相等,基于operator==。set::insert对“相同”的定义是等价,通常基于operator<(!w1<w2 && !w2<w1)。
2. 操作上来说,相等的概念是基于operator==的
等价是基于在一个有序区间中对象值的相对位置,两个对象x和y如果在关联容器c的排序顺序中没有哪个排在另一个之前,那么它们关于c使用的排序顺序有等价的值。
3. 每个标准关联容器通过它的key_comp成员函数来访问排序判断式,所以如果下式求值为真,两个对象x和y关于一个关联容器c的排序标准有等价的值:
- !c.key_comp()(x, y) && !c.key_comp()(y, x) // 在c的排序顺序中
- // 如果x在y之前它非真,
- // 同时在c的排序顺序中
- // 如果y在x之前它非真
3.2 为指针的关联容器指定比较类型
=================================
1. 无论何时你建立一个指针的标准关联容器,你必须记住容器会以指针的值排序。这基本上不是你想要的,所以你几乎总是需要建立自己的仿函数类作为比较类型。
通常对于T*,你需要定义自己的比较仿函数类less<T*>,并在定义关联容器时指定这个仿函数:
- struct StringPtrLess:
- public binary_function<const string*, // 使用这个基类
- const string*, // 的理由参见条款40
- bool> {
- bool operator()(const string *ps1, const string *ps2) const
- {
- return *ps1 < *ps2;
- }
- };
然后你可以使用StringPtrLess作为ssp的比较类型:
- typedef set<string*, StringPtrLess> StringPtrSet;
- StringPtrSet ssp; // 建立字符串的集合,
- // 按照StringPtrLess定义的顺序排序
- ... // 和前面一样插入
- // 同样四个字符串
3.3 永远让比较函数相等的值返回false
====================================
1. 如果让比较函数对相等的值返回true,那么会有什么后果?
由于关联容器是用!compare(w1,w2) && !compare(w2,w1) 来判断w1和w2是否相等的,而若compare返回true,那么该比较结果就为false,那么就能过插入多个相同的值进入关联容器,这回破坏关联容器!!
2. 从技术上说,用于排序关联容器的比较函数必须在它们所比较的对象上定义一个“严格的弱序化(strict weak ordering)”。
而任何一个定义了严格的弱序化的函数都必须在传入相同的值的两个拷贝时返回false。
3.4 避免原地修改set和multiset的键
==================================
1. 正如所有标准关联容器,set和multiset保持它们的元素有序,这些容器的正确行为依赖于它们保持有序。 如果你改了关联容器里的一个元素的值(例如,把10变为1000),新值可能不在正确的位置,而且那将破坏容器的有序性。
2. 如果你改变set或multiset里的元素, 你必须确保不改变一个键部分——影响容器有序性的元素部分。
3. 如果你要总是可以工作而且总是安全地改变set、multiset、map或multimap里的元素,按五个简单的步骤去做:
* 定位你想要改变的容器元素。如果你不确定最好的方法,条款45提供了关于怎样进行适当搜寻的指导。
* 拷贝一份要被修改的元素。对map或multimap而言,确定不要把副本的第一个元素声明为const。毕竟,你想要改变它!
* 修改副本,使它有你想要在容器里的值。
* 从容器里删除元素,通常通过调用erase(参见条款9)。
* 把新值插入容器。如果新元素在容器的排序顺序中的位置正好相同或相邻于删除的元素,使用insert的“提示”形式把插入的效率从对数时间改进到分摊的常数时间。使用你从第一步获得的迭代器作为提示。
- EmpIDSet se; // 同前,se是一个以ID号
- // 排序的雇员set
- Employee selectedID; // 同前,selectedID是一个带有
- // 需要ID号的雇员
- ...
- EmpIDSet::iterator i =
- se.find(selectedID); // 第一步:找到要改变的元素
- if (i!=se.end()){
- Employee e(*i); // 第二步:拷贝这个元素
- se.erase(i++); // 第三步:删除这个元素;
- // 自增这个迭代器以
- // 保持它有效(参见条款9)
- e.setTitle("Corporate Deity"); // 第四步:修改这个副本
- se.insert(i, e); // 第五步:插入新值;提示它的位置
- // 和原先元素的一样
- }
3.5 考虑用有序vector代替关联容器
=================================
1. 对于多数应用,被认为是常数时间查找的散列容器要好于保证了对数时间查找的set、map和它们的multi同事。
2. 标准关联容器的典型实现是平衡二叉查找树,
一个平衡二叉查找树是一个对插入、删除和查找的混合操作优化的数据结构,它比较适合插入、删除和查找都是混合在一起的操作。
一般来说,没有办法预测对树的下一个操作是什么。
3. 在实际应用中,有些应该的操作只是两步:1) 建立:插入和删除数据.2) 查找:查找数据.
一个vector可能比一个关联容器能提供更高的性能(时间和空间上都是)。但不是任意的vector都会,只有有序vector。
4. 当使用vector来模拟map<K, V>时,保存在vector中数据的类型将是pair<K, V>,而不是pair<const K, V>。
因为当你对vector排序时,它的元素的值将会通过赋值移动,那意味着pair的两个组件都必须是可赋值的。
3.6 当关乎效率时,应该在map::operator[]和map-insert之间仔细选择
===============================================================
1. 对于map操作,当“增加”被执行时,insert比operator[]更高效。
当我们做更新时,情形正好相反.
3.7 熟悉非标准散列容器
=======================
1. 在标准C++库里没有任何散列表
2. SGI设计的hash_container中一个值得注意的方面是使用equal_to作为默认比较函数。这违背标准关联容器的约定——默认比较函数是less
对于散列容器来说,这不是一个不合理的决定,因为散列关联容器,不像它们在标准中的(通常基于树)兄弟,不需要保持有序。
本文出自 “暗日” 博客,请务必保留此出处http://darksun.blog.51cto.com/3874064/1162471