关于map查找的问题,自定义结构体作为key,为什么只重载“<”就可以了

dearpanan 2007-11-02 10:00:25
例如:
typedef struct CoupleTag
{
CString code;
CString name;
bool operator < ( const CoupleTag rhs) const {return code<rhs.code;}
}Couple;

typedef std::map<Couple,Couple> CoupleMap;

调用CoupleMap.find(...),可以找到指定值。但是为什么只用重载"<"操作符。
为什么不用重载"==",问题很弱。

有人可以解释下么?
...全文
3253 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
getcodekkk 2011-09-06
  • 打赏
  • 举报
回复
小样1111111111111111111111111111111111
酒鬼 2011-04-28
  • 打赏
  • 举报
回复
其实感觉“weiym”说的比较清楚了。可能楼主是对那个一长串文章中说到的find靠“==”感觉迷糊。其实他在后面的解释了的。find其实有两个,一个是算法的find,一个是map成员的find,而你调用的是成员的。成员的find是靠“<”,泛型算法的find靠的是"=="。不知说的对错。学习ing
iambic 2007-11-05
  • 打赏
  • 举报
回复
我晕,这还用调试。不提供操作符看它报什么错不就知道了。
jxlczjp77 2007-11-05
  • 打赏
  • 举报
回复
我晕,这个都讨论了这么久。

楼主你自己试试,重载一个"== " ,再重载一个 " < ",然后在这两个函数上都设个断点调试,看看到底用的是哪个不就行了吗。

我试过后的结果是,map只会用到 " < ",根本不需要 "=="。
loops 2007-11-04
  • 打赏
  • 举报
回复 1
在map的实现里面就是靠 对调operator<两边的操作数实现的。
set的源码如下:

_Nodeptr _Lbound(const _K& _Kv) const
{_Nodeptr _X = _Root();
_Nodeptr _Y = _Head;
while (_X != _Nil)
if (key_compare(_Key(_X), _Kv)) //就是operator<调用的地方
_X = _Right(_X); //往右边子树走
else
_Y = _X, _X = _Left(_X); //记录父节点,往左边子树走
return (_Y); }


iterator find(const _K& _Kv)
{iterator _P = lower_bound(_Kv); //这个_P就是_Lbound返回的_Y
return (_P == end()
|| key_compare(_Kv, _Key(_P._Mynode())) //把_P再和_Kv对调了比较一下
? end() : _P); }


_Kv就是键值,注意_LBound里面,_Kv都是在后面来比较的,而find里面,最后还放在前面比较了一下,可以==就是靠两次<来完成比较的。



叶落无心 2007-11-04
  • 打赏
  • 举报
回复
default Operator ==,是判断你的数据成员是不是都相等,如果都相等,那==就是true
而> < 就不知道怎么判断了
iambic 2007-11-04
  • 打赏
  • 举报
回复
!(a < b) && !(b < a)
=>
a == b
weiym 2007-11-02
  • 打赏
  • 举报
回复
条款19:了解相等和等价的区别
STL充满了比较对象是否有同样的值。比如,当你用find来定位区间中第一个有特定值的对象的位置,find必须可以比较两个对象,看看一个的值是否与另一个相等。同样,当你尝试向set中插入一个新元素时,set::insert必须可以判断那个元素的值是否已经在set中了。

find算法和set的insert成员函数是很多必须判断两个值是否相同的函数的代表。但它们以不同的方式完成,find对“相同”的定义是相等,基于operator==。set::insert对“相同”的定义是等价,通常基于operator<。因为有定义不同,所以有可能一个定义规定了两个对象有相同的值而另一个定义判定它们没有。结果,如果你想有效使用STL,那么你必须明白相等和等价的区别。

操作上来说,相等的概念是基于operator==的。如果表达式“x == y”返回true,x和y有相等的值,否则它们没有。这很直截了当,但要牢牢记住,因为x和y有相等的值并不意味着所有它们的成员有相等的值。比如,我们可能有一个内部记录了最后一次访问的Widget类。

class Widget {
public:
...

private:
TimeStamp lastAccessed;
...
};
我们可以有一个用于Widget的忽略这个域的operator==:

bool operator==(const Widget& lhs, const Widget& rhs) {
// 忽略lastAccessed域的代码
}
在这里,两个Widget即使它们的lastAccessed域不同也可以有相等的值。

等价是基于在一个有序区间中对象值的相对位置。等价一般在每种标准关联容器(比如,set、multiset、map和multimap)的一部分——排序顺序方面有意义。两个对象x和y如果在关联容器c的排序顺序中没有哪个排在另一个之前,那么它们关于c使用的排序顺序有等价的值。这听起来很复杂,但实际上,它不。考虑一下,举一个例子,一个set<Widget> s。两个Widget w1和w2,如果在s的排序顺序中没有哪个在另一个之前,那么关于s它们有等价的值。set<Widget>的默认比较函数是less<Widget>,而默认的less<Widget>简单地对Widget调用operator<,所以w1和w2关于operator<有等价的值如果下面表达式为真:

!(w1 < w2) // w1 < w2时它非真
&& // 而且
!(w2<w1) // w2 < w1时它非真
这个有意义:两个值如果没有哪个在另一个之前(关于某个排序标准),那么它们等价(按照那个标准)。

在一般情况下,用于关联容器的比较函数不是operator<或甚至less,它是用户定义的判断式。(关于判断式的更多信息参见条款39。)每个标准关联容器通过它的key_comp成员函数来访问排序判断式,所以如果下式求值为真,两个对象x和y关于一个关联容器c的排序标准有等价的值:

!c.key_comp()(x, y) && !c.key_comp()(y, x) // 在c的排序顺序中
// 如果x在y之前它非真,
// 同时在c的排序顺序中
// 如果y在x之前它非真
表达式!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不在y之前时为真。

要完全领会相等和等价的含义,考虑一个忽略大小写的set<string>,也就是set的比较函数忽略字符串中字符大小写的set<string>。这样的比较函数会认为“STL”和“stL”是等价的。条款35演示了怎么实现一个函数,ciStringCompare,它进行了忽略大小写比较,但set要一个比较函数的类型,不是真的函数。要天平这个鸿沟,我们写一个operator()调用了ciStringCompare的仿函数类:

struct CIStringCompare: // 用于忽略大小写
public // 字符串比较的类;
binary_function<string, string, bool> { // 关于这个基类的信息
// 参见条款40
bool operator()(const string& lhs,
const string& rhs) const
{
return ciStringCompare(lhs, rhs); // 关于ciStringCompare
} // 是怎么实现的参见条款35
}
给定CIStringCompare,要建立一个忽略大小写的set<string>就很简单了:

set<string, CIStringCompare> ciss; // ciss = “case-insensitive
// string set”
如果我们向这个set中插入“Persephone”和“persephone”,只有第一个字符串加入了,因为第二个等价于第一个:

ciss.insert("Persephone"); // 一个新元素添加到set中
ciss.insert("persephone"); // 没有新元素添加到set中如果我们现在使用set的find成员函数搜索字符串“persephone”,搜索会成功,

if (ciss.find("persephone") != ciss.end())... // 这个测试会成功但如果我们用非成员的find算法,搜索会失败:

if (find(ciss.begin(), ciss.end(),
"persephone") != ciss.end())... // 这个测试会失败那是因为“persephone”等价于“Persephone”(关于比较仿函数CIStringCompare),但不等于它(因为string("persephone") != string("Persephone"))。这个例子演示了为什么你应该跟随条款44的建议优先选择成员函数(就像set::find)而不是非成员兄弟(就像find)的一个理由。

你可能会奇怪为什么标准关联容器是基于等价而不是相等。毕竟,大多数程序员对相等有感觉而缺乏等价的感觉。(如果不是这样,那就不需要本条款了。)答案乍看起来很简单,但你看得越近,就会发现越多问题。

标准关联容器保持有序,所以每个容器必须有一个定义了怎么保持东西有序的比较函数(默认是less)。等价是根据这个比较函数定义的,所以标准关联容器的用户只需要为他们要使用的任意容器指定一个比较函数(决定排序顺序的那个)。如果关联容器使用相等来决定两个对象是否有相同的值,那么每个关联容器就需要,除了它用于排序的比较函数,还需要一个用于判断两个值是否相等的比较函数。(默认的,这个比较函数大概应该是equal_to,但有趣的是equal_to从没有在STL中用做默认比较函数。当在STL中需要相等时,习惯是简单地直接调用operator==。比如,这是非成员find算法所作的。)

让我们假设我们有一个类似set的STL容器叫做set2CF,“set with two comparison functions”。第一个比较函数用来决定set的排序顺序,第二个用来决定是否两个对象有相同的值。现在考虑这个set2CF:

set2CF<string, CIStringCompare, equal_to<string> > s; 在这里,s内部排序它的字符串时不考虑大小写,等价标准直觉上是这样:如果两个字符串中一个等于另一个,那么它们有相同的值。让我们向s中插入哈迪斯强娶的新娘(Persephone)的两个拼写:

s.insert("Persephone");
s.insert("persephone");
着该怎么办?如果我们说"Persephone" != "persephone"然后两个都插入s,它们应该是什么顺序?记住排序函数不能分别告诉它们。我们可以以任意顺序插入,因此放弃以确定的顺序遍历set内容的能力吗?(不能已确定的顺序遍历关联容器元素已经折磨着multiset和multimap了,因为标准没有规定等价的值(对于multiset)或键(对于multimap)的相对顺序。)或者我们坚持s的内容的一个确定顺序并忽略第二次插入的尝试(“persephone”的那个)? 如果我们那么做,这里会发生什么?

if (s.find("persephone") != s.end())... // 这个测试成功或失败?大概find使用了等价检查,但如果我们为了维护s中元素的一个确定顺序而忽略了第二个insert的调用,这个find会失败,即使“persephone”的插入由于它是一个重复的值的原则而被忽略!

总之,通过只使用一个比较函数并使用等价作为两个值“相等”的意义的仲裁者,标准关联容器避开了很多会由允许两个比较函数而引发的困难。一开始行为可能看起来有些奇怪(特别是当你发现成员和非成员find可能返回不同结果),但最后,它避免了会由在标准关联容器中混用相等和等价造成的混乱。

有趣的是,一旦你离开有序的关联容器的领域,情况就变了,相等对等价的问题会——已经——重临了。有两个基于散列表的非标准(但很常见)关联容器的一般设计。一个设计是基于相等,而另一个是基于等价。我鼓励你转到条款25去学更多这样的容器和设计以决定该用哪个。
weiym 2007-11-02
  • 打赏
  • 举报
回复
同意LS,具体可参考 effective stl item19
http://stl.winterxy.com/html/item_19.html
jxlczjp77 2007-11-02
  • 打赏
  • 举报
回复
map并没不需要==号,只要小于号就行
jxlczjp77 2007-11-02
  • 打赏
  • 举报
回复
只用<号也能判断相等啊:
if( (!(a<b) && !(b<a) )
   //ok,a==b
else
//a!=b
weiym 2007-11-02
  • 打赏
  • 举报
回复
weiym, 明白你的意思,不过我只重载了“ <”,还是可以成功调用 find 函数。 这是为什么?
--------

你调用的find函数是Map的成员函数吧,类似map.find(a)这种形式,它只需要operator < 就够了啊,当然能成功。
我刚才在VS2003里试了调用算法的find,类似find(map.begin(), map.end(), a)这种形式,编译都通不过,如果换成set容器倒是可以的。
反正是能用容器的成员函数代替就不要用算法,成员函数都是针对每种容器本身特别提供的,应优先使用。
BluntBlade 2007-11-02
  • 打赏
  • 举报
回复
因为CString已经重载了==,而结构体本身只有两个CString,所以可以生成一个默认的==。如果某个数据成员没有重载==,那就必须为结构体重载==。
飞哥 2007-11-02
  • 打赏
  • 举报
回复
它要是自动生成我就更不放心了
飞哥 2007-11-02
  • 打赏
  • 举报
回复
重载小于就能使map有序
至于==也是需要的


如果你是自定义类型,怎么能相信它能判断出来? 还得 重载==
dearpanan 2007-11-02
  • 打赏
  • 举报
回复
如果能够自动生成“==”,也可以生成其他的操作符吧,就不需要自己重载了。 觉得不是这样的吧
ckt 2007-11-02
  • 打赏
  • 举报
回复
你重载了<
就知道如何比较两个结构
就能排列啦
BluntBlade 2007-11-02
  • 打赏
  • 举报
回复
编译器会默认给你生成一个,如果它能生成的话。
t88266236 2007-11-02
  • 打赏
  • 举报
回复
cstring类型有默认的==操作符函数,如果是其他没有这个操作符的类型就会出错.
dearpanan 2007-11-02
  • 打赏
  • 举报
回复
weiym, 明白你的意思,不过我只重载了“<”,还是可以成功调用 find 函数。 这是为什么?
加载更多回复(2)

64,701

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧