在C++中重载赋值运算符

3
我知道网上有很多关于重载赋值运算符的例子,但我已经花了几个小时试图在我的程序中实现它们并弄清楚它们的工作原理,但似乎无法做到,希望能得到任何帮助。
我正在尝试实现一个重载赋值运算符函数。
我已经有了3个文件:Complex.h头文件,Complex.cpp定义文件和一个用作测试Complex类的驱动的.cpp文件。
在Complex.h头文件中,我的赋值运算符函数原型如下:
Complex &operator= (Complex&);

到目前为止,我在我的.cpp文件中对重载运算符的定义如下:

    Complex &Complex::operator=(Complex& first)
    {
         if (real == first.real && imaginary == first.imaginary)
             return Complex(real, imaginary);

         return first;
    };

我在函数中调用赋值运算符的方式是:

   x = y - z;

具体来说,我的问题是当我使用x = y -z调用重载的赋值运算符时,它没有将传入的值赋给x,而当我返回传入的值时,我无法从网上的众多示例和解释中找出原因。非常感谢任何帮助,并提前感谢您的任何帮助。


1
为什么不阅读一些实例并以实际有效的操作符为模型来编写你的赋值运算符呢? - juanchopanza
= 不应该修改它的右操作数。 - emlai
@juanchopanza 我尝试过了,我为此道歉并真诚地感谢您的建议,但我的大脑就是无法这样工作,我确实尝试过阅读那些示例并模仿那些有效的示例,但最终会陷入递归。 - Prog
@zenith,你的意思是说通过先返回我修改了右操作数? - Prog
它可以被修改,但他实际上并没有修改它。 - Puppy
显示剩余2条评论
3个回答

3
我认为您需要以下运算符定义。
Complex & Complex::operator =( const Complex &first )
{
     real = first.real;
     imaginary = first.imaginary;

     return *this;
};

你需要返回被赋值对象的引用,并且参数应该是一个常量引用。在这种情况下,你可以将引用绑定到一个临时对象,或者你必须写一个移动赋值运算符。

在你的复制赋值运算符实现中,你没有改变被赋值的对象。:) 你只是创建了一个临时对象并返回对这个临时对象的引用,或者返回对第一个对象的引用。


虽然严格来说是正确的,但并不是很有帮助,因为它并没有真正向 OP 解释你为什么以那种方式编写它。 - Puppy
如果我正确理解你的方法,我应该将实部和虚部私有数据成员分配给创建一个新的复数对象,然后使用当前(this)数据成员的地址返回创建的复数对象? - Prog
@DastardlyDeedDoer 不,赋值操作不应该创建一个新对象(除非作为中间实现细节)。它是将一个值分配给现有对象。 - juanchopanza
@juanchopanza 你是说我通过返回first创建了一个新对象吗?但是因为我已经用它调用了重载运算符,所以它不是已经被创建了吗?所以我只是返回了传入的值?还是我的理解有误? - Prog
@DastardlyDeedDoer 不,我的意思是你不需要创建一个新对象,也不应该这样做。我的意思是,你在你的错误代码中这样做了,但是这个答案中的代码没有这样做。 - juanchopanza
好的,我现在明白了,非常感谢。我将把这个回答标记为答案,因为我尝试过它并且运行良好,但是@πάντα ῥεῖ也有一个可行的回答。我非常感激所有的输入和你们的快速响应,非常感谢。 - Prog

1

赋值需要两个对象,一个是被赋值的对象,另一个是具有所需值的对象。

赋值应该:

  • 修改现有的对象,即被赋值的对象

赋值不应该:

  • 创建新的对象
  • 修改具有所需值的对象。

假设我们要处理的对象类型是:

struct S {
  int i;
  float f;
};

一个执行赋值操作的函数可能具有以下签名:
void assignment(S &left_hand_side, S const &right_hand_side);

它将被使用:

S a = {10, 32.0f};

S b;

assignment(b, a);

注意:

  • 左侧对象是可修改的,赋值将修改它,而不是创建一个新对象。
  • 右侧对象,即具有所需值的对象,不可修改。

此外,在C++中,内置的赋值操作是一个表达式,而不是语句;它具有一个值,并且可以用作子表达式:

int j, k = 10;

printf("%d", (j = k));

这将j设置为k,然后取该赋值的结果并将其打印出来。需要注意的重要事项是,赋值表达式的结果是被分配的对象。不会创建新对象。在上面的代码中,打印了j的值(因为j刚刚从k获得了值10)。
将我们之前的赋值函数更新以遵循此约定会产生以下签名:
S&assignment(S&left_hand_side,S const&right_hand_side);
实现如下:
S &assignment(S &left_hand_side, S const &right_hand_side) {
  // for each member of S, assign the value of that member in 
  // right_hand_side to that member in left_hand_side.
  left_hand_side.i = right_hand_side.i;
  left_hand_side.f = right_hand_side.f;

  // assignment returns the object that has been modified
  return left_hand_side;
}

请注意,此赋值函数不是递归的;它不会在赋值中使用自己,但它会使用成员类型的赋值。
解决难题的最后一步是让语法“a = b”生效,而不是“assignment(a, b)”。为了做到这一点,你所要做的就是将“assignment()”变成一个成员函数。
struct S {
  int i;
  float f;

  S &assignment(S &left_hand_side, S const &right_hand_side) {
    left_hand_side.i = right_hand_side.i;
    left_hand_side.f = right_hand_side.f;
    return left_hand_side
  }
};

使用*this替换left_hand_side参数:

struct S {
  int i;
  float f;

  S &assignment(S const &right_hand_side) {
    this->i = right_hand_side.i;
    this->f = right_hand_side.f;
    return *this;
  }
};

将函数重命名为operator=:
struct S {
  int i;
  float f;

  S &operator=(S const &right_hand_side) {
    this->i = right_hand_side.i;
    this->f = right_hand_side.f;
    return *this;
  }
};

int main() {
  S a, b = {10, 32.f};

  S &tmp = (a = b);

  assert(a.i == 10);
  assert(a.f == 32.f);

  assert(&tmp == &a);
}

另一个重要的事情是要知道,=符号在一个非赋值的地方被使用:
S a;
S b = a; // this is not assignment.

这是“复制初始化”。它不使用'operator=',不是赋值。请尽量不要混淆两者。
需要记住的要点:
- 赋值修改被分配对象。 - 赋值不修改被分配对象。 - 赋值不创建新对象。 - 赋值不是递归的,除了复合对象的分配使用其所有包含的小成员对象的分配之外。 - 赋值在修改值后返回对象。 - 复制初始化看起来有点像赋值,但与赋值无关。

我知道这篇文章已经发布了一段时间,但这是一个非常周到的答案,应该被保存下来。 - Don Shanil

0

重载赋值运算符应该长成这样:

Complex &Complex::operator=(const Complex& rhs)
{
     real = rhs.real; 
     imaginary = rhs.imaginary;
     return *this;
};

你还需要注意,如果你重载了赋值运算符,那么你也应该以同样的方式重载 Complex 的拷贝构造函数:
Complex::Complex(const Complex& rhs)
{
    real = rhs.real; 
    imaginary = rhs.imaginary;
};

@DastardlyDeedDoer:“我需要在赋值运算符函数返回值时修改左操作数(即x)。” 我的示例将完全做到这一点。 - πάντα ῥεῖ
如果你有像 Complex a,b; a = b; 这样的代码,你认为会传递给 first 参数的是 a 还是 b - πάντα ῥεῖ
需要将一个名为b的复数对象赋值给复数对象a。 - Prog
@Puppy 好的,我已经移除了它。所以你的意思是现代编译器会在调用赋值运算符之前对其进行优化? - πάντα ῥεῖ
1
@Puppy 有一个非常重要的用例,你必须检查自我分配:面试。;-) - Greenflow
显示剩余7条评论

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