19. 理解相等(equality)和等价(equivalence)的区别

STL中,对两个对象进行比较,看他们的值是否相同,涉及到两个概念,相等和等价。

  • 相等的概念是基于operator == 的。如果表达式"x ==y"返回真,则x和y的值相等,否则就不相等。
  • 等价是以“在已排序的区间中对象值的相对顺序”为基础的。

考虑std::set s,如果两个Widget w1和w2,在s的排列顺序中哪个也不在另外一个的前面,那么,w1和w2对于s而言有等价的值。std::set的默认比较函数是std::less, 而在默认情况下std::less只是简单地调用了针对Widget的operator <, 所以,如果下面的表达式结果为真,则w1和w2对于operator < 有等价的值:

!(w1 < w2) && !(w2 < w1)

这里的含义是:如果两个值中的任何一个(按照一定的排序准则)都不在另外一个的前面,那么这两个值(按照这一准则)就是等价的。

在一般情形下,一个关联容器的比较函数并不是operator <, 甚至也不是less,它是用户定义的判别式(predicate)。
每个标准关联容器都通过key_compare成员函数是排序判别式可被外部使用,所以,如果下面的表达式为true,则按照关联容器c的排序准则,两个对象x和y有等价的值:

!c.key_compare()(x, y) && !c.key_compare()(y, x)

为了充分领会相等和等价的区别,考虑一个不区分大小写的比较函数IgnoreCasLess,它把"PERSEPHONE"和"persephone"看成是等价的。

std::string ConvertToLower(const std::string& one)
{
	...
}
auto IgnoreCaseLess = [](const std::string& lhs, const std::string& rhs) { ConvertToLower(lhs) < ConvertToLower(rhs);};

创建一个不区分大小学的std::set容器datas。

std::set<std::string, IgnoreCaseLess> datas;

如果我们把字符串"PERSEPHONE"和"persephone"插入到集合datas中,则只有第一个字符串会被插入,因为第二个和第一个等价:

datas.insert("PERSEPHONE"); // 插入
datas.insert("persephone"); // 插入失败

如果我们使用std::set的成员函数find()来查找字符串"persephone",则该查找会成功:

if (datas.find("persephone") != datas.end()) ... // 成功找到的是**"PERSEPHONE"**

但如果我们使用非成员的find()算法,则查找失败:

if (std::find(datas.begin(), datas.end(), "persephone") != datas.end()) // 找不到

这是因为"PERSEPHONE"与"Persephone"等价(按照IgnoreCaseLess),但并不相等。

因此,对于序列容器(set multiset map multimap)应该优先选用自己的成员函数而不是与之对应的算法,这样才能保证都是使用的是用于插入的排序函数来进行的相应处理,保证结果的一致性。

那么,关联容器为什么是基于等价而不是相等的。毕竟,绝大多数人对于相等有一种直觉,而对于等价则不然。关联容器总是保持排序的,所以每个容器必须有一个比较函数来决定保持这样的顺序。等价的定义正是通过比较函数确定的。因此,关联容器要为使用的每个容器指定一个比较函数。如果该关联容器使用相等来决定两个对象是否相等,那么每个关联容器除了应用于排序的比较函数外,还需要另外一个比较函数来决定两个对象是否相等。总之,使用单一的比较函数(用于排序),并把等价关系作为判定两个元素是否"相同"的依据,使得标准关联容器避免了一大堆“若使用两个比较函数带来的问题”,这样做避免了在标准关联容器中混合使用相等和等价带来的混乱。

你可能感兴趣的:(Effective,STL)