条款19
相等和等价的区别
相等的概念是基于operator==的。如果表达式“x == y”返回true,x和y有相等的值,否则它们没有。
等价的概念是基于operator< 的。等价是基于在一个有序区间中对象值的相对位置。等价一般在每种标准关联容器(比如,set、multiset、map和multimap)的一部分——排序顺序方面有意义。两个对象x和y如果在关联容器c的排序顺序中没有哪个排在另一个之前,那么它们关于c使用的排序顺序有等价的值。
如:标准库中的find 和set 容器的成员函数find 的区别?
条款20
为指针的关联容器指定比较类型
set<string*> ssp; // ssp = “set of string ptrs” ssp.insert(new string("Anteater")); ssp.insert(new string("Wombat")); ssp.insert(new string("Lemur")); ssp.insert(new string("Penguin"));
copy(ssp.begin(), ssp.end(), // 把ssp中的字符串 ostream_iterator<string>(cout, "\n")); // 拷贝到cout(但这 // 不能编译)
set<string*> 并不一定会按照 A , W, L, P顺序排列打印。我们必须写自己的排列算法来代替默认的。
struct StringPtrLess: public binary_function<const string*, const string*, bool>
{
bool operator() (const string* sp1, const string* sp2) const
{
return *sp1 < *sp2;
}
};
typedef set<string*, StringPtrLess> stringPtrSet;
StringPtrSet ssp;
条款21
永远让比较函数对相等的值返回false
set<int, less_equal<int> > s;
s.insert(10);
s.insert(10);
结果如何:????
通常的结果是set以拥有了两个为10的值的拷贝而告终,也就是说它不再是一个set了。通过使用less_equal作为我们的比较类型,我们破坏了容器!此外,任何对相等的值返回true的比较函数都会做同样的事情。
struct StringPtrGreater: // 高亮显示 public binary_function<const string*, // 这段代码和89页的改变 const string*, // 当心,这代码是有瑕疵的! bool> { bool operator()(const string *ps1, const string *ps2) const { return !(*ps1 < *ps2); // 只是相反了旧的测试; } // 这是不对的! };
multiset<int, less_equal<int> > s; // s仍然以“<=”排序
s.insert(10); // 插入10A
s.insert(10); // 插入
10B
现在,s里有两个10的拷贝,因此我们期望如果我们在它上面做一个equal_range,我们将会得到一对指出包含这两个拷贝的范围的迭代器。但那是不可能的。equal_range,虽然叫这个名字,但不是指示出相等的值的范围,而是等价的值的范围。在这个例子中,s的比较函数说10A
和10B
是不等价的,所以不可能让它们同时出现在equal_range所指示的范围内。
你明白了吗?除非你的比较函数总是为相等的值返回false,你将会打破所有的标准关联型容器,不管它们是否允许存储复本。
条款22
避免原地修改set 和multiset
因为set或multiset里的值不是const,所以试图改变它们可以编译。本条款的目的是提醒你如果你改变set或multiset里的元素, 你必须确保不改变一个键部分——影响容器有序性的元素部分。如果你做了,你会破坏容器,再使用那个容器将产生未定义的结果, 而且那是你的错误。另一方面,这个限制只应用于被包含对象的键部分。对被包含元素的所有其他部分来说,是开放的:随便改变!
这是同一个累人的雇员例子,这次以安全、可移植的方式写:
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); // 第五步:插入新值;提示它的位置 // 和原先元素的一样 }
条款23
考虑用有序vector 代替关联容器
需要一个提供快速查找的数据结构时,很多STL程序员立刻会想到标准关联容器:set、multiset、map和multimap。直到现在这很好,但不是永远都好。
如果查找速度真得很重要,的确也值得考虑使用非标准的散列容器(参见条款25)。如果使用了合适的散列函数,则可以认为散列容器提供了常数时间的查找。
即使你需要的就只是对数时间查找的保证,标准关联容器仍然可能不是你的最佳选择。和直觉相反,对于标准关联容器,所提供的性能也经常劣于本该比较次的vector。如果你要有效使用STL,你需要明白什么时候和怎么让一个vector可以提供比标准关联容器更快的查找。
条款24
当关乎效率时应该在map::operator[]和map-insert之间仔细选择
如果你要更新已存在的map元素,operator[]更好,但如果你要增加一个新元素,insert则有优势。