如何比较结构体

6

我在设置比较时遇到了困难。

以下是我的问题示例,我的代码错误地假设{1,2} = {2,1}:http://ideone.com/i7huL

#include <iostream>
#include <map>
using namespace std;

struct myStruct {
  int a;
  int b;
  bool operator<(const myStruct& rhs) const {
           return rhs.a < this->a && rhs.b < this->b;
  }
};


int main() {
       std::map  <myStruct, int> mymap ;
       myStruct m1={1,2};
       myStruct m2={2,1};
       mymap.insert(make_pair(m1,3));
       std::map<myStruct, int>::iterator it1 = mymap.find(m1);
       std::map<myStruct, int>::iterator it2 = mymap.find(m2);
       cout << it1->second << it2->second;
       // here it1->second=it2->second=3, although I would have expected it2 to be equal to map.end().
}

我可以使用||代替&&,但我不确定这是否是正确的方法。我只想实现operator<,以便我能够在我的映射中找到对象,而没有任何错误发生,就像我链接的代码一样。

谢谢。


参数被称为 rhs 是有原因的... 右边。但是你目前测试 rhs < lhs。看到问题了吗? - dowhilefor
你说得对,那里有一个小错误。但这有关系吗?只是一切的顺序被倒置了,但这并不能解释为什么 {1,2}={2,1},或者可以吗? - NaomiJO
1
不,这并不重要。但我认为你的问题并不是真正的问题,只是一个设计决策。你需要清楚地定义当MyStruct“比另一个小”时的情况。较小是否意味着“两个成员都较小”、“任一成员较小”、“第一个成员较小”,这取决于你和你需要它做什么。 - dowhilefor
问题在于我并不关心一个对象是否比另一个对象小或大,我只想能够在mymap中找到myStruct对象。问题是,当以这种方式定义时,我无法找到它们,find()会出错,正如你所看到的那样。那么如何正确地定义operator<呢? - NaomiJO
Hans Passant已经给出了正确的答案。 - jahhaj
可能是stl less operator and "invalid operator<" error的重复问题。 - ildjarn
7个回答

8

是的,这个操作符实现没有太多意义。我建议:

  bool operator<(const myStruct& rhs) const {
      return rhs.a < this->a || (rhs.a == this->a && rhs.b < this->b);
  }

这似乎相当复杂。你能解释一下它背后的思想,以及如何将其扩展到 myStruct 中的 4 个 int 成员吗? - NaomiJO
那就是我试着用我的定义来做的。它有什么不同? - NaomiJO
@NaomiJO,你的是反过来的。 - chris
@NaomiJO 抱歉,我并不是说这不重要。我只是想说这不是我们现在需要解决的问题。它仍然很重要,因为 A < B != B < A。 - dowhilefor
1
这完全取决于你想如何排序结构体,而你在问题中并没有解释清楚。我给出的示例非常常见,例如在电话簿中排序姓名。姓氏最重要,名字次之。只有当姓相同时才按名字排序。将你使用的规则应用到电话簿示例中,看看会发生什么。 - Hans Passant
显示剩余5条评论

8
bool operator<(const myStruct& rhs) const {
  if (a < rhs.a) return true;
  if (a == rhs.a) return b < rhs.b;
  return false;
}

如果您正在寻找一种泛化到多个数据成员的方法,那么可以使用C++11 std::tie 进行示例:

struct S {
    int n;
    std::string s;
    float d;
    bool operator<(const S& rhs) const {
        return std::tie(n, s, d) < std::tie(rhs.n, rhs.s, rhs.d);
    }
};

5
问题在于您的运算符没有定义严格弱序。请仔细思考您的示例{1,2}{2,1}在您的运算符中的表现。假设X={1,2}Y={2,1}
X < Y吗?是1<2 并且 2<1吗?不是,因此X不小于Y。
Y < X吗?是2<1 并且 1<2吗?不是,因此Y不小于X。
因此,如果X不小于Y,而Y也不小于X,那么剩下的是什么?它们相等。

你需要选择你的结构体中的一个成员,要么是a,要么是b,作为主要比较。如果主要比较结果相等,然后才检查次要比较。就像当你 按字母顺序排序 一样。首先检查第一个字母,只有在它们相等时才继续下一个。Hans Passant提供了一个例子。

以下是一个更严重的问题示例,适用于你的运算符。我上面给出的不一定是坏的,因为也许你希望{1,2}被视为等于{2,1}。根本性的问题出现在这样一组值中:考虑X = {1,1},Y = {1,2},Z = {2,2}

使用您的运算符,X 明显小于 Z,因为 1 小于 2。但 X 等于 Y,Y 等于 Z。为了遵循严格弱序规则,如果 X = Y,且 Y = Z,则 X 应等于 Z。但在这里不是这种情况。


3
您问及如何将代码推广到四个 int 成员变量,以下是我认为最清晰的结构化代码方式。

您问及如何将代码推广到四个 int 成员变量,以下是我认为最清晰的结构化代码方式。

bool operator<(const myStruct& rhs) const
{
  if (a < rhs.a)
    return true;
  if (a > rhs.a)
    return false;
  if (b < rhs.b)
    return true;
  if (b > rhs.b)
    return false;
  if (c < rhs.c)
    return true;
  if (c > rhs.c)
    return false;
  if (d < rhs.d)
    return true;
  if (d > rhs.d)
    return false;
  return false;
}

您可以轻松地扩展此类代码,以满足您希望的任意数据成员。


a > rhs.a 替换为 rhs.a < a 可以带来轻微的好处,因为这只需要从成员类中调用 operator<() - Brangdon

1
最简单的解决方案是使用std::tie来比较元组。
return std::tie(rhs.a, rhs.b) < std::tie(a, b);

这个很快、很简单地推广到更多的数据成员。


1

我更喜欢通过比较元素的相等性来编写代码,直到找到两个不同的元素为止:

bool operator<(const myStruct& rhs) const {
    if (a != rhs.a)
        return a < rhs.a;
    if (b != rhs.b)
        return b < rhs.b;
    return false; // this and rhs are equal.
}

我认为这种方法比使用 || 和 && 的混合表达式(如@HansPassant所述)更清晰、更易于扩展,比@jahhaj的方法更紧凑,后者需要每个传递测试都导致return true;return false;。性能大致相同,除非您了解值的分布情况。有一个避免使用operator==()而只使用operator<()的论点,但这仅适用于尝试编写最大程度通用模板代码的情况。


0
问题在于您需要知道您的结构体代表什么。否则,定义一个<运算符将变得随意。其他人无法给出适当的答案。举个例子,当您的结构体表示2D点的笛卡尔坐标时。在这种情况下,您可以定义一个有意义的排序运算符,例如结构体到原点的距离。
即,距离d1 = this->a*this->a + this->b*this->b 距离d2 = rhs.a*rhs.a + rhs.b*rhs.b 如果(d1 < d2) 返回真; 否则 返回假;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接