C++:重载数学运算符

3
我想重载加法运算符,让它能够将同一类的两个对象相加。当我在头文件中的类声明中声明这个“operator +”原型函数时,我将两个对象作为参数传递。然而,编译器报错,提示“二进制'operator +'有太多的参数”。我在网上搜索答案后发现,在头文件的类声明外部声明内联函数可以解决问题。我想知道我做错了什么或者是否漏掉了什么。以下是我在头文件中使用的代码。
class Person
{
private:
    int age;
    double weight;
public:
    Person::Person();                           //default constructor       
    Person::~Person();                           //default desctructor
    Person operator+(Person r, Person i);
};

这段代码编译时会出现我之前提到的错误。以下是编译正常的代码:

class Person
{
private:
    int age;
    double weight;
public:
    Person::Person();                           //default constructor       
    Person::~Person();                           //default desctructor
};
inline Person operator+(Person r, Person i)
{
return Person(0,0); 
}

请注意,添加人员不是每个人都熟悉的操作,因此重载 operator+() 对它来说很可能没有意义。有关更多详细信息,请参阅我的答案。这是一个好问题,我给你点赞。 - sbi
我只是为了举例子而使用了一个Person类,以便足够简单地表达我的观点。实际上,我正在使用一个复数类,并且不想讨论为什么我不使用已经创建的Complex类。 - Brundle
这让你开始讨论是否对于一个Person类来说重载operator+()有意义。:) 是的,对于一个Complex类来说,重载算术运算符非常有意义。 - sbi
8个回答

8
如果您将 oparator+ 声明为实例函数,则第一个参数将被传递为 this 对象,因此您只需要一个更多的参数。阅读此文以获取更多信息,特别是尝试理解 const 概念:http://www.cs.caltech.edu/courses/cs11/material/cpp/donnie/cpp-ops.html
如引用文章所建议的最佳方法是:
class Person
{
    ...
    Person &operator+=(const Person &i);
    const Person operator+(const Person &i) const;
    ...
};

Person &Person::operator+=(const Person &i) {
    ...   // Do the compound assignment work.
    return *this;
}

const Person Person::operator+(const Person &i) const {    
    Person result = *this;     // Make a copy of myself.  Same as MyClass result(*this);
    result += i;            // Use += to add other to the copy.
    return result;              // All done!
}

如果您决定使用const版本,请记住,您只能在thisi引用上调用const方法。这是首选方式。
我参考的文章首先解释了重载+=的概念,然后更详细地使用+=定义+。这是一个好主意,因为必须单独重载+=运算符。
此外,David Rodríguez建议无论是否存在+=,都应将operator+实现为自由函数。

5
我对不鼓励使用 "const" 表示不满,我认为使用 "const" 是 C++ 程序员工具箱中最强大的工具之一,同时你几乎不可能因使用它而受到伤害。它是语言中最伟大的特性之一,应该在任何可能的情况下鼓励使用。 - sbi
2
阻止使用const/鼓励使用非const版本...这是一个可怕的建议。从语义上讲,a+b不应修改ab,因此将其设置为const最有意义。我发现你最后一次编辑很有趣(用+=实现+,但仍然没有建议将+实现为自由函数,而不管是否存在+=)。 - David Rodríguez - dribeas
我希望你喜欢这个版本更多。 - agsamek
1
@sbi:const 真是让人烦透了。我花费的时间更多地用来标记那些根本不需要 const 的东西,而不是从 const 中获得任何好处。如果 const 不存在,我会更加快乐。此外,返回 const rvalue 没有任何意义或价值。 - Puppy
不,即使是非const的右值也是非法的。(a + b) = c; 是一个错误。operator= 的左操作数必须是一个左值。 - Puppy
显示剩余8条评论

6

每个成员函数都有一个隐式的第一个参数:this。操作符也是如此。因此,如果您想将operator+()设置为成员函数,则它必须仅接受一个参数,即右操作数,因为左操作数已经是this

然而,对于那些既可以是成员函数也可以是自由函数的二元运算符,我的经验法则是使其成为自由函数,用对称地处理其参数(operator+()不会改变其任何操作数),并使那些不这样做的二元运算符成为成员函数(operator+=()改变其左操作数,但不改变其右操作数)。
此外,对于所有算术运算符,都有一种基于它们的组合分配版本来实现它们的好模式。也就是说,operator+()将基于operator+=()-将基于-=()等。
还要注意,您不希望毫无意义地复制类对象,而通常希望它们通过const引用传递。

基于此,我编写的operator+的规范版本将是:

class Person
{
public:
    Person& operator+=(const Person& i)
    {
      // whatever
      return *this;
    }
};

inline Person operator+(Person lhs, const Person& rhs)
{
  lhs += rhs; // note: lhs passed per copy
  return lhs;
}

然而, 关于运算符重载,可能最重要的经验法则是:不要这么做。这听起来可能有些矛盾,但通常命名函数比运算符更好,因为后者使代码不太易读。在编写代码时,可读性应该是最重要的方面除非运算符在应用领域具有明确且无争议的含义,否则不要为类重载它
在您的示例中,将运算符+应用于两个人肯定没有干净且无争议的含义。在现实世界中,将两个人相加意味着什么?(我能想到的第一个含义不会产生单个人,而是一组人。:)
因此,除非为您的Person类重载此运算符是作业,否则我强烈建议不要这样做。(如果这是一个任务,我会责怪老师找到这样一个糟糕的例子。)


2

以下是一个类中使用 += 的示例。

Vector3D& operator+=( const Vector3D& other ) {
        x+=other.x;
        y+=other.y;
        z+=other.z;
        return *this;
    }

你不想通过复制来获取向量。 - sbi
我想展示如何使用 += 运算符。这个版本可以工作,但是现在它只是一个参考,让人们感到高兴。 - InsertNickHere
好的,我已经取消了我的踩。请注意,当你回答一个问题时,你通常是在有效地教别人如何做他们不知道如何做的事情。如果你省略了一些你认为与答案无关的内容,他们就不会学会如何看待它。 - sbi
@sbi 好的,我会在写下一篇答案时考虑到这个。 - InsertNickHere

1
据我所读,最佳实践是将operator+声明为友元函数而不是成员函数。
在cplusplus.com上有关于这个问题的整个讨论: 二元运算符作为友元——为什么? Meyers也在他的书《Effective C++》中讨论了这个问题。 引用来自geustgulkan的评论
对于任何给定的二元运算符,左手对象执行操作。
假设我们有一个名为ClassX的类,它对整数的+运算符进行了重载。然后,如果我们创建该类的对象(称其为objx),那么我们可以说objx + 2。objx将执行操作并返回ClassX的结果对象。
但是,如果我们说2 + objx呢?结果应该与2 + objx相同,但整数不是类,数字文字当然也不是类 - 因此不能直接使用2 + objx。
如果我们必须设计我们的代码以确保我们始终在+的左侧具有ClassX的对象,每当我们想要将int添加到它时,这将是一种痛苦。
这就是我们使用ClassX的operator+友元重载的地方。
class ClassX
{

    friend ClassX operator+(ClassX objx, int i);
    friend ClassX operator+(int i, ClassX objx);
};

所以你可能想要的是:

class Person
{
private:
    int age;
    double weight;
public:
    Person::Person();                           //default constructor       
    Person::~Person();                           //default desctructor
    friend Person operator+(const Person &rhs, const Person &lhs);
};

Person operator+(const Person &lhs, const Person &rhs) {
     // do whatever constitutes addition here
     Person temp = lhs;
     temp.weight += rhs.weight;
     return temp;  // return copy of temp
}

我感觉我在重复自己:-1:operator+ 不应该 修改 lhs。(顺便说一下,在你的例子中这甚至不可能,因为你是通过const引用传递lhs)。它的原型是Person operator+(const Person&,const Person&) - mtvec
@工作:抱歉,我没有注意到 - 我已经修复了。 - Robert S. Barnes

1
我只有一个建议:使用Boost.Operators。
class Person: boost::addable<Person>      // yes private inheritance
{
public:
  Person& operator+=(Person const& rhs);

private:
};

就我所见,有两个优点:

  • 无需编写样板代码,因此不会出错;)
  • 使接口清晰,addable几乎是不言自明的

Boost.Operators提供了一个参考表,其中列出了每个行为应定义的必需运算符和将自动生成的运算符。

将接口保持紧凑并将一些文档注入到代码中非常方便。


0
 class Person
  {
    private:
     int age;
     double weight;
    public:
      Person();                           //default constructor       
      ~Person();                           // destructor
      Person operator+(const Person &r); //overloaded operator as member function, so only one argument is required to be passed
 };

实现:

  Person Person::operator+(const Person &r)
  {
      Person x;
      x.age=this->age + r.age; // this refers to the object on which the function has been invoked, P1 in this case(see below)
      return x;
  }

当您将二进制运算符(在此情况下为+)作为成员函数进行重载时,隐式传递了调用该运算符的对象到该函数中,并且您只需要显式传递一个参数即可。
因此,您的主要函数应该像这样:
 int main()
 {
    Person P1,P2;
    P2=P1+P2; //P1 is passed implicitly 

    //The above expression is equivalent to P2=P1.operator+(P2);
}

“operator+” 不应该修改 “this”。如果你将其设为“const”,编译器就会捕捉到你的错误。 - mtvec
@Job:感谢您指出这一点。我的实现方式确实很简单。现在,您能否请取消您的负评? - Satish
当然,我会。不过还有一个小错误:应该是 Person Person::operator+ - mtvec

0

我同意agsamek和sbi的论据。 我添加了一些额外的考虑。

  1. 正如您所看到的,您的Person类没有任何包含2个参数的构造函数,这是一个错误(但尚未被编译器指出)
  2. 通常当您重载一个数学运算符时,您还需要重载“operator =”和构造函数;
  3. 如果您想使用两个参数重载加号运算符,则不能使用实例成员,而必须使用函数。 我避免声明友元函数,因为这会破坏该类的封装性(维护起来更困难)。 最好实现一些“访问器方法”以获取对象的内部值。
  4. 在某些编译器中,内联方法需要在同一位置(通常在.h文件中)进行声明和定义,因此不易维护。 而且该代码的扩展取决于编译器。

接下来是我的解决方案:

class Person
{
private:
    int age;
    double weight;
public:
    Person(){}                           //default constructor       
    Person(int a, double w) : age(a), weight(w) {}
    ~Person(){}                           //default desctructor

    int getAge() const { return age; }
    double getWeight() const { return weight; }

    const Person & operator =(const Person & rha) {
        age = rha.age; weight = rha.weight; return *this;
    }
};

Person operator +(const Person & lha, const Person & rha)
{
    return Person(lha.getAge() + rha.getAge(), rha.getWeight() + lha.getAge());
}

int _tmain(int argc, _TCHAR* argv[])
{
    Person p1(30,75);
    Person p2(40,80);

    Person sumOfPis = p1 + p2;  // <-- Without "operator =" this statement will use the default copy constructor

    return 0;
}

0

看起来您声明了成员运算符+,但没有意图引用任何Person实例数据:成员运算符作为任何成员方法一样,接受调用它的实例作为参数。

如果出于某种原因需要将其设置为成员(例如访问私有状态),则可以将其声明为静态。


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