C++开发初级


884 浏览 5 years, 3 months

3.3 对象赋值

版权声明: 转载请注明出处 http://www.codingsoho.com/

对象赋值

就像可将一个int值赋给另一个int一样,在C++中也可以将一个对象的值赋给另一个对象。例如,下面的代码将myCell的值赋给了anotherCell:

SpreadsheetCell myCell(5), anotherCell;
anotherCell = myCell;

您或许会说myCell被“复制”给了anotherCell。然而在C++中,“复制”只在初始化对象时发生。如果一个已经具有值的对象被改写,更精确的术语是“赋值”。

注意C++提供的复制工具是复制构造函数。因为这是一个构造函数,因此只能用在创建对象的时候,而不能用在对象的赋值上。

因此,C++为所有的类提供了执行赋值的方法。这个方法叫做赋值运算符(assignment operator) ,其名称是operator=,因为实际上是为类重载了=运算符。在前面的示例中,调用了anotherCell的赋值运算符,参数为myCell。

在C++11标准中,本节所讲的赋值运算符有时也被称为复制赋值运算符(copy assignment operator),因为在赋值之后,左边的对象以及右边的对象都继续存在。之所以要这样区别,是因为C++11添加了一个新的移动赋值运算符(move assignment operator),为了提高性能,当复制结束后右边的对象会被销毁。移动赋值运算符将在后面讨论。

如果您没有编写自己的赋值运算符,C++将自动生成一个,从而允许将对象赋给另一个对象。默认的C++赋值行为几乎与默认的复制行为相同:以递归方式用源对象的数据成员给目标对象赋值,当然语法稍有不同。

声明赋值运算符

在此是另一个版本的SpreadsheetCell类定义,其中包含了赋值运算符:

class SpreadsheetCell
{
 public:
  SpreadsheetCell& operator=(const SpreadsheetCell& rhs);
};

代码取自 SpreadsheetCellAssign\SpreadsheetCell.h

赋值运算符与复制构造函数类似,采用了源对象的const引用。在此情况下,将源对象称为rbs,代表等号的“右边”,调用赋值运算符的对象在等号的左边。

与复制构造函数不同的是,赋值运算符返回SpreadsheetCell对象的引用。原因是赋值可以连续使用,如下所示:

myCell = anotherCell = aThirdCell;

当执行这一行的时候,第一件事情就是anotherCell调用赋值运算符,aThirdCell是“右边”的参数。随后myCell调用赋值运算符。然而,此时anotherCell并不是参数。右边的值是将aThirdCell赋值给anotherCell时赋值运算符的返回值。如果赋值运算符不返回一个结果,myCell将无法赋值。

您可能怀疑为什么myCell的赋值运算符不能将anotherCell当作参数。原因是等号实际上是方法调用的缩写。当用完整的函数语法看待这行时,就会发现问题:

myCell.operate= (anotherCell.operate= (aThirdCell));

现在可以看到,anotherCell调用的operator=必须返回一个值,这个值会传递给myCell调用的operator=。正确的返回值是anotherCell本身,这样它就可以赋值给myCell的源对象。然而,直接返回anotherCell效率不高,因此返回一个anotherCell的引用。

提示: 实际上您可以让赋值运算符返回任意类型,包括void。然而您应该返回被调用对象的引用,因为客户希望这样。

定义赋值运算符

赋值运算符的实现与复制构造函数类似,但也存在一些重要的区别。

首先,复制构造函数只有在初始化的时候才调用,此时目标对象还没有有效的值。赋值运算符可以改写对象的当前值。在为对象动态分配内存之前,可以不考虑这个问题,后面将详细讨论这个主题。

其次,在c++中允许将对象的值赋给自身。例如,下面的代码可以编译并运行:

SpreadsheetCell cell(4);
cell = cell; //self-assignment

赋值运算符不应该阻止自赋值,也不应该在自赋值的时候执行完整的操作。这样,赋值运算符应该在方法开始的时候检测自赋值,如果发现自赋值,则立刻返回。

下面是SpreadsheetCell类赋值运算符的定义:

SpreadsheetCell& SpreadsheetCell::operator=(const SpreadsheetCell& rhs)
{
  if (this == &rhs) {
}

代码取自 SpreadsheetCellAssign\SpreadsheetCell.cpp

前面的行检测自赋值,但是有一点神秘之处。当等号的左边和右边相同的时候,就是自赋值。判断两个对象是否相同的方法之一是他们在内存中的位置是否相同—更明确地说,是指向它们的指针是否相等。

对象调用任何方法时,都会使用指向这个对象的this指针。因此,this是一个指向左边对象的指针。与此类似,&rhs是一个指向右边对象的指针。如果这两个指针相等,赋值必然是自赋值,但由于返回值是SpreadsheetCell&,因此必须返回一个正确的值。所有赋值运算符都返回*this,自赋值情况也不例外:

    return *this;
  }

this指针指向执行方法的对象,因此*this就是对象本身。编译器将返回一个对象的引用,从而与声明的返回值匹配。如果不是自赋值,您必须对每个成员赋值:

  mValue = rhs.mValue;
  mString = rhs.mString;

这个方法在这里复制了值。

  return *this;
}

最后返回*this,前面己经对此做过解释。

完整代码

SpreadsheetCell& SpreadsheetCell::operator=(const SpreadsheetCell& rhs)
{
  if (this == &rhs) {
    return *this;
  }
  mValue = rhs.mValue;
  mString = rhs.mString;
  return *this;
}

代码取自 SpreadsheetCellAssign\SpreadsheetCell.cpp

显式默认或者删除赋值运算符(仅限C++11)

可以显式地默认或者删除编译器生成的赋值运算符,如下所示:

  SpreadsheetCell& operator=(const SpreadsheetCell& rhs) = default;

或者

  SpreadsheetCell& operator=(const SpreadsheetCell& rhs) = delete;