⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 item_19.html

📁 制作本书的目的是为了方便大家的阅读。转载时请保持本电子书的完整性。 前言、条款2、16、21、44根据从Addison-Wesley出版社下载的开放条款翻译。条款26、27、28、45根据从Sc
💻 HTML
字号:
<?xml version="1.0" encoding="gb2312"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><title>条款19:了解相等和等价的区别</title><meta http-equiv="Content-Type" content="text/html; charset=gb2312" /><link href="css/all.css" rel="stylesheet" type="text/css" /><link rel="stylesheet" href="http://stl.winterxy.com/styles-site.css" type="text/css" /><link rel="alternate" type="application/rss+xml" title="RSS" href="http://stl.winterxy.com/index.rdf" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://stl.winterxy.com/rsd.xml" /></head><body><div id="banner"><h1><a href="http://stl.winterxy.com/" accesskey="1">Center of STL Study</a> </h1><span class="description">——最优秀的STL学习网站<script language="javascript" src="http://www.winterxy.com/cgi-bin/js/webstats.js"></script></span></div><h2>条款19:了解相等和等价的区别</h2> <p>STL充满了比较对象是否有同样的值。比如,当你用find来定位区间中第一个有特定值的对象的位置,find必须可以比较两个对象,看看一个的值是否与另一个相等。同样,当你尝试向set中插入一个新元素时,set::insert必须可以判断那个元素的值是否已经在set中了。</p> <p>find算法和set的insert成员函数是很多必须判断两个值是否相同的函数的代表。但它们以不同的方式完成,find对“相同”的定义是<em>相等</em>,基于operator==。set::insert对“相同”的定义是<em>等价</em>,通常基于operator&lt;。因为有定义不同,所以有可能一个定义规定了两个对象有相同的值而另一个定义判定它们没有。结果,如果你想有效使用STL,那么你必须明白相等和等价的区别。</p> <p>操作上来说,相等的概念是基于operator==的。如果表达式“x == y”返回true,x和y有相等的值,否则它们没有。这很直截了当,但要牢牢记住,因为x和y有相等的值并不意味着所有它们的成员有相等的值。比如,我们可能有一个内部记录了最后一次访问的Widget类。</p> <pre>class Widget {public:	...private:	TimeStamp lastAccessed;	...};</pre> <p>我们可以有一个用于Widget的忽略这个域的operator==:</p> <pre>bool operator==(const Widget&amp; lhs, const Widget&amp; rhs) {	// 忽略lastAccessed域的代码}</pre> <p>在这里,两个Widget即使它们的lastAccessed域不同也可以有相等的值。</p> <p>等价是基于在一个有序区间中对象值的相对位置。等价一般在每种标准关联容器(比如,set、multiset、map和multimap)的一部分——排序顺序方面有意义。两个对象x和y如果在关联容器c的排序顺序中没有哪个排在另一个之前,那么它们关于c使用的排序顺序有等价的值。这听起来很复杂,但实际上,它不。考虑一下,举一个例子,一个set&lt;Widget&gt; s。两个Widget w1和w2,如果在s的排序顺序中没有哪个在另一个之前,那么关于s它们有等价的值。set&lt;Widget&gt;的默认比较函数是less&lt;Widget&gt;,而默认的less&lt;Widget&gt;简单地对Widget调用operator&lt;,所以w1和w2关于operator&lt;有等价的值如果下面表达式为真:</p><pre>!(w1 &lt; w2)				// w1 &lt; w2时它非真&amp;&amp;					// 而且!(w2&lt;w1)					// w2 &lt; w1时它非真</pre> <p>这个有意义:两个值如果没有哪个在另一个之前(关于某个排序标准),那么它们等价(按照那个标准)。</p> <p>在一般情况下,用于关联容器的比较函数不是operator&lt;或甚至less,它是用户定义的判断式。(关于判断式的更多信息参见<a href="item_39.html">条款39</a>。)每个标准关联容器通过它的key_comp成员函数来访问排序判断式,所以如果下式求值为真,两个对象x和y关于一个关联容器c的排序标准有等价的值:</p> <pre>!c.key_comp()(x, y) &amp;&amp; !c.key_comp()(y, x)		// 在c的排序顺序中						// 如果x在y之前它非真,						// 同时在c的排序顺序中						// 如果y在x之前它非真</pre> <p>表达式!c.key_comp()(x, y)看起来很丑陋,但一旦你知道c.key_comp()返回一个函数(或一个函数对象),丑陋就消散了。!c.key_comp()(x, y)只不过是调用key_comp返回的函数(或函数对象),并把x和y作为实参。然后对结果取反,c.key_comp()(x, y)仅当在c的排序顺序中x在y之前时返回真,所以!c.key_comp()(x, y)仅当在c的排序顺序中x<em>不在</em>y之前时为真。</p> <p>要完全领会相等和等价的含义,考虑一个忽略大小写的set&lt;string&gt;,也就是set的比较函数忽略字符串中字符大小写的set&lt;string&gt;。这样的比较函数会认为“STL”和“stL”是等价的。<a href="item_35.html">条款35</a>演示了怎么实现一个函数,ciStringCompare,它进行了忽略大小写比较,但set要一个比较函数的<em>类型</em>,不是真的函数。要天平这个鸿沟,我们写一个operator()调用了ciStringCompare的仿函数类:</p> <pre>struct CIStringCompare:					// 用于忽略大小写	public						// 字符串比较的类;	binary_function&lt;string, string, bool&gt; {		// 关于这个基类的信息							// 参见<a href="item_40.html">条款40</a>	bool operator()(const string&amp; lhs,			const string&amp; rhs) const	{		return ciStringCompare(lhs, rhs);		// 关于ciStringCompare	}						// 是怎么实现的参见<a href="item_35.html">条款35</a>}</pre> <p>给定CIStringCompare,要建立一个忽略大小写的set&lt;string&gt;就很简单了:</p> <pre>set&lt;string, CIStringCompare&gt; ciss;		// ciss = “case-insensitive					// string set”</pre> <p>如果我们向这个set中插入“Persephone”和“persephone”,只有第一个字符串加入了,因为第二个等价于第一个:</p> <pre>ciss.insert(&quot;Persephone&quot;);		// 一个新元素添加到set中ciss.insert(&quot;persephone&quot;);		// 没有新元素添加到set中</pre> <p>如果我们现在使用set的find成员函数搜索字符串“persephone”,搜索会成功,</p> <pre>if (ciss.find(&quot;persephone&quot;) != ciss.end())...		// 这个测试会成功</pre> <p>但如果我们用非成员的find算法,搜索会失败:</p> <pre>if (find(ciss.begin(), ciss.end(),		&quot;persephone&quot;) != ciss.end())...		// 这个测试会失败</pre> <p>那是因为“persephone”等价于“Persephone”(关于比较仿函数CIStringCompare),但不等于它(因为string(&quot;persephone&quot;) != string(&quot;Persephone&quot;))。这个例子演示了为什么你应该跟随条款44的建议优先选择成员函数(就像set::find)而不是非成员兄弟(就像find)的一个理由。</p> <p>你可能会奇怪为什么标准关联容器是基于等价而不是相等。毕竟,大多数程序员对相等有感觉而缺乏等价的感觉。(如果不是这样,那就不需要本条款了。)答案乍看起来很简单,但你看得越近,就会发现越多问题。</p> <p>标准关联容器保持有序,所以每个容器必须有一个定义了怎么保持东西有序的比较函数(默认是less)。等价是根据这个比较函数定义的,所以标准关联容器的用户只需要为他们要使用的任意容器指定一个比较函数(决定排序顺序的那个)。如果关联容器使用相等来决定两个对象是否有相同的值,那么每个关联容器就需要,除了它用于排序的比较函数,还需要一个用于判断两个值是否相等的比较函数。(默认的,这个比较函数大概应该是equal_to,但有趣的是equal_to从没有在STL中用做默认比较函数。当在STL中需要相等时,习惯是简单地直接调用operator==。比如,这是非成员find算法所作的。)</p> <p>让我们假设我们有一个类似set的STL容器叫做set2CF,“set with two comparison functions”。第一个比较函数用来决定set的排序顺序,第二个用来决定是否两个对象有相同的值。现在考虑这个set2CF:</p> <pre>set2CF&lt;string, CIStringCompare, equal_to&lt;string&gt; &gt; s; </pre> <p>在这里,s内部排序它的字符串时不考虑大小写,等价标准直觉上是这样:如果两个字符串中一个等于另一个,那么它们有相同的值。让我们向s中插入哈迪斯强娶的新娘(Persephone)的两个拼写:</p> <pre>s.insert(&quot;Persephone&quot;);s.insert(&quot;persephone&quot;);</pre> <p>着该怎么办?如果我们说&quot;Persephone&quot; != &quot;persephone&quot;然后两个都插入s,它们应该是什么顺序?记住排序函数不能分别告诉它们。我们可以以任意顺序插入,因此放弃以确定的顺序遍历set内容的能力吗?(不能已确定的顺序遍历关联容器元素已经折磨着multiset和multimap了,因为标准没有规定等价的值(对于multiset)或键(对于multimap)的相对顺序。)或者我们坚持s的内容的一个确定顺序并忽略第二次插入的尝试(“persephone”的那个)? 如果我们那么做,这里会发生什么?</p> <pre>if (s.find(&quot;persephone&quot;) != s.end())...	// 这个测试成功或失败?</pre> <p>大概find使用了等价检查,但如果我们为了维护s中元素的一个确定顺序而忽略了第二个insert的调用,这个find会失败,即使“persephone”的插入由于它是一个重复的值的原则而被忽略!</p> <p>总之,通过只使用一个比较函数并使用等价作为两个值“相等”的意义的仲裁者,标准关联容器避开了很多会由允许两个比较函数而引发的困难。一开始行为可能看起来有些奇怪(特别是当你发现成员和非成员find可能返回不同结果),但最后,它避免了会由在标准关联容器中混用相等和等价造成的混乱。</p> <p>有趣的是,一旦你离开<em>有序的</em>关联容器的领域,情况就变了,相等对等价的问题会——已经——重临了。有两个基于散列表的非标准(但很常见)关联容器的一般设计。一个设计是基于相等,而另一个是基于等价。我鼓励你转到<a href="item_25.html">条款25</a>去学更多这样的容器和设计以决定该用哪个。</p> </body></html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -