本节书摘来自异步社区出版社《C++面向对象高效编程(第2版)》一书中的第3章,第3.4节,作者: 【美】Kayshav Dattatri,更多章节内容可以访问云栖社区“异步社区”公众号查看。

3.4 赋值操作符

C++面向对象高效编程(第2版)
现在,让我们分析main程序中的语句:

a = b ;  // 将一个栈赋值给另一个栈
在该语句中,我们将对象b赋值给对象a,使用赋值操作符完成赋值操作。如果a和b都是简单的整数,无论a中的值是什么,编译器都会用b中的值将其擦写(overwrite)。就是这么简单。但是在该例中,a和b都是我们创建的对象,这意味着由我们负责赋值操作。我们知道TIntStack类对象之间如何进行赋值,并且能够实现赋值操作符。对于任何赋值操作符,都应注意以下几点:

(1)确保对象没有自我赋值(如a = a)。

(2)复用被赋值对象中的资源或销毁它。

(3)从源对象中将待复制内容复制到目的对象。

(4)最后,返回对目的对象的引用。

赋值操作符已在类中声明。TIntStack类的赋值操作符签名如下:

TIntStack& operator=(TIntStack& source) // ①
TIntStack& operator=(const TIntStack& source) // ②```
下面是赋值操作符的实现:

TIntStack& TIntStack::operator=(const TIntStack& source)
{
  // 检查自我复制(即a = a)。
  // source是被赋值的对象,&source给出对象source的地址,
  // ‘this’是被赋值对象的地址。
  if (this == &source) {
      cout << “Warning: Assignment to self.n”;
      return *this;
  }
   /*
    如果源对象栈中的元素数目小于或等于目的对象的大小,则没有任何问题。
    我们要做的就是复制栈中相应的元素和_count变量。
    但是,如果源对象栈中元素的数目大于目的对象的大小,则必须先删除目的对象_sp中内存,再为其分配新的内存(与源对象栈中元素的数目相等),然后复制所有元素。
   */
  if (source._count > this->_size) {
     // 源对象中元素的数目大于目的对象的大小,
     // 删除_sp中的内存并重新分配内存。
     // 见下文解释
      delete [] _sp;
      this->_size = source._size;
      _sp = new int [ this->_size ];
  }
    // 无论是小于等于还是大于的情况,都会用到以下代码遍历栈,
    // 并复制元素
    for (int i = 0; i < source._count; i++)
      this->_sp[i] = source._sp[i]; // 复制元素
    this->_count = source._count;
    return *this; // 见下文解释
}`
分析:赋值操作由程序员显式完成,系统(编译器)绝不会直接调用它。赋值是在两个现有对象之间执行的操作。如下语句:
`
a = b;`
当a和b都为对象时,上句可解析为:
`
operator=(b);`
换言之,左侧(left hand side,缩写为LHS)的对象调用成员函数operator=(),右侧(right hand side,缩写为RHS)操作数作为operator=()的参数。赋值操作右侧的对象(该例中为b)不能被修改,该操作的副作用(side effect)是:返回对左侧对象(该例中为a)的引用。因为不能修改右侧的对象(我们只能读取右侧对象并写入左侧对象),所以该对象应作为const实参传递给operator=()函数(如②所示)。然而,如果使用没有const参数的赋值操作符(如①所示),就可以被修改右侧的对象。我们并不推荐使用①这样的赋值操作符,因为类的用户并不希望在赋值操作中改变右侧的对象。由于a和b都是真正的现有对象,因此,将b赋值给a时,a中的值将被b中的值擦写(overwrite)。

记住:

如果我们在类中未提供赋值操作符,编译器会为类生成一个默认赋值操作符(编译器绝不会自动调用它,但确实会生成一个)。但是,这样的默认赋值操作符可能无法满足我们的要求,下一章将对此作详细介绍。

在任何赋值操作中,我们要做的就是将数据从源对象赋值到目的对象。一般而言,这很容易,但在某些情况下会有困难。在TIntStack的示例中,我们需要从源栈赋值到目的栈。如果源栈中的元素数目少于或等于目的栈中的可用空间,赋值操作便非常简单,只需复制相应的栈元素。但是,如果源栈中的元素数目多于目的栈所能持有的数目会怎样?我们不允许这样的操作发生,在打印出错误消息后将从赋值操作返回。不过,这样限制太大。或者,我们可以改变目的栈的大小,使其能容纳所有元素。如果我们接受后者的方案,就需要在目的栈上为源栈的元素分配与源栈大小相等的新内存。这样做之前,我们还要删除目的栈中_sp所指向的现有内存(无法扩展这个内存),这就是上面的赋值操作符中调用delete所完成的任务。一旦完成内存分配,我们就只需要复制相应的元素进栈即可(见图3-3和图3-4)。

(摘自前面的main程序)

TIntStack a(5);  // 自动对象(_auto object_),在退出main时被销毁
TIntStack b(20);  // 另一个自动对象```
最后,我们返回对目的对象(也就是栈a)的引用。你可能会质疑,为什么要这样做?假设我们编写了一个级联赋值操作(cascaded assignment operation)如下:`c = a = b; // 将b赋值给a,然后将结果再赋值给c`
赋值操作按从右到左的顺序执行1,此表达式即转换为:`c = (a = b); // 首先将b赋值给a,然后将结果赋值给c`
警告:
既然可以写“a = b;”,那么也可以写`a = a;  // 语法上正确,但是逻辑上错误!`
这样的表达式可能会通过指针和引用(别名)直接或间接地发生,我们的实现必须检查是否出现这种情况。因此,必须核实源地址和目的地址是否相同。如果赋值发生在相同的对象之间,则不会进行任何操作,只返回对目的对象的引用。这就是上面示例的实现中,进行自我赋值检查所完成的工作。如果没有进行自我赋值检查,可能会导致严重问题。考虑以下包含指针数据成员的TString类:

class TString {
    public:
       TString (const char* sp) {
         _data = new char[strlen(sp) + 1];
          strcpy(_data, sp);
       }
       TString & operator=(const TString & assign);
    private:
       char* _data; // 字符串指针
};
TString& TString::operator=(const TString& assign)
{
    // 错误代码,未检查自我赋值  
  delete [] this->_data; // ①
   data = new char[strlen(assign.data) + 1]; // ②
   strcpy(this->_data, assign._data);
   return *this;
}
TString x1(“Text string1”);
x1 = x1;`
以上operator=的实现并不安全,其中的①,删除的是x1._data。接着在②中,我们又试图使用assign._data。因为左侧的对象(*this)与右侧的对象(assign)是同一个对象,即assign._data与x1._data相同。所以,我们搬起石头砸自己的脚。当执行②时,该程序可能崩溃,或者在别的地方出现问题。因此,在赋值操作的实现中,必须进行自我赋值检查。

图3-4

将b赋值给a非常简单,之前的示例中已介绍过赋值如何工作。我们希望将赋值(b赋值给a)的结果再赋值给c。为使其正常工作,第1个赋值操作应返回a的值(或对a的引用)。否则,从a到c的第2次赋值就会出问题。通常,在C和C++中,我们只知道使用赋值操作的结果,却并不了解内部的实现。例如,正是因为赋值操作的副作用,才使得以下代码可以正常工作:

int i = 10, j = 20; // 此处无赋值操作
int k;
cout << (i = j) << endl; // 将j赋值给i,并打印赋值结果①。if (k = j) { // 将j赋值给k,如果结果不为0,执行if体内部的语句②。// 省略此处代码
}```
在①中,完成赋值后返回i的值,并打印该值。与此类似,在②中,将j赋值给k,完成赋值后,返回k的值并与0进行比较。为了让以上赋值都能正常工作,必须确保在赋值操作完成后,该赋值操作返回对a对象的引用。`Pascal:`在类似Pacal的语言中,以下的赋值操作没有副作用:`a := b;`
在赋值后无法返回a的值。因此,以下语句`c := a := b;`
无法在Pascal(以及类似的语言,如Modula-2)中使用。思考:对比复制构造函数和赋值操作符中的代码,会发现很多相似的地方。它们都将现有对象复制到另一个对象中。但是,你能否发现两处代码的主要差别?换言之,复制构造函数和赋值操作符的代码有何区别?能否让它们共享实现?为完成TIngStack的示例,其余的成员函数实现如下:

void TIntStack::Push(int what)
{
   if (_count < _size) {  // 如有更多空间储存元素
    _sp[_count] = what;  // 储存元素
    _count++;  // 递增count
   }
   else {
    cout << “Stack is FULL. Cannot Push value” << what << endl;
   }
}
int TIntStack::Pop()
{
   if (_count <= 0) { // 栈为空
     cout << “Stack is EMPTYn”;
     exit (1);  // 如果失败如何报错?可在此处抛出异常。
   }
   _count--;
   return _sp[_count];
}
unsigned TIntStack::HowMany() const
{
   return _count;
}`
1此处用到操作符重载和操作符结合律,将在第8章中介绍。在本章讨论中,只需记住赋值从右向左结合。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

《C++面向对象高效编程(第2版)》——3.4 赋值操作符相关推荐

  1. 《C++面向对象高效编程(第2版)》——3.11 类名、成员函数名、参数类型和文档...

    本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第3章,第3.11节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...

  2. 《C++面向对象高效编程(第2版)》——2.29 泛化关系(is-a)

    本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第2章,第2.29节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...

  3. 《C++面向对象高效编程(第2版)》——2.20 什么是多线程安全类

    本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第章,第2.20节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区& ...

  4. 《C++面向对象高效编程(第2版)》——2.30 has-a关系的重要性

    本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第2章,第2.30节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...

  5. 《C++面向对象高效编程(第2版)》——导读

    前言 C++面向对象高效编程(第2版) 面向对象软件开发已逐渐成为开发软件的首选.优秀的面向对象软件开发人员.设计人员.系统架构师对其需求与日俱增.要想成为一名成功的面向对象编程(OOP)人员必须忘却 ...

  6. 《C++面向对象高效编程(第2版)》——3.13 采用语义

    本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第3章,第3.13节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...

  7. 《C++面向对象高效编程(第2版)》——3.17 编写内存安全类

    本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第3章,第3.17节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...

  8. 《C++面向对象高效编程(第2版)》——2.16 识别成员函数的目标对象

    本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第2章,第2.26节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...

  9. 《C++面向对象高效编程(第2版)》——3.16 从函数中返回引用

    本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第3章,第3.16节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...

最新文章

  1. msys2 config.guess: unable to guess system type
  2. html用a标签怎么提交表单?
  3. JS - JSON对象与JSON字符串相互转换的几种方法
  4. flask中数据库的基本操作-增删改查【备忘】
  5. P2P中的NAT穿越方案简介
  6. 12.20今日头条实习面试总结-算法工程师
  7. 创新, FMA amp; SMA 世界第一台VCD机的故事
  8. flutter跨平台开发之App升级方案
  9. JAVA中properties基本用法
  10. 士林变频器面板如何调速度_必读干货丨西威变频器DRIVE OVERLOAD故障处理
  11. icem合并面网格_详解Flunet Meshing:高级网格划分工具中的“扫地僧”
  12. 计算机网络期末4小时速成
  13. 小波 奇异点 matlab,Matlab小波变换对于奇异点的检测.doc
  14. layout-v21透明主题不生效的bug
  15. 商场抽奖软件 android,召唤抽奖系统3.0正式版
  16. 扫描中如何实现自动纠偏
  17. 区块链技术:未来将颠覆的9大行业
  18. mysql支持存储emjoy表情
  19. 办公室日常管理信息系统 数据库课程设计
  20. 在java中,OOA是什么?OOD是什么?OOP是什么?

热门文章

  1. 计算机应用与技术网上测试题答案,计算机应用与技术网上测试题.doc
  2. 浙江大学计算机与机械工程,中国26所“机械工程”大学经调整、合并,浙大、西交、同济升级...
  3. 事务的四个特性-ACID
  4. 剑指Offer Ⅱ 003.二进制加法(力扣剑指Offer专项突击版——整数_3)
  5. 安卓使用Span富文本给某段Text文本加上波浪线
  6. linux挂载盘符扫描,Linux下挂载ISCSI的盘符问题
  7. 硕士本科论文通过matlab出仿真图
  8. matlab缩小矩阵尺寸,Matlab中对矩阵使用矩阵尺寸的索引
  9. linux内核匹配的编译器gcc,linux下正则匹配的C实现
  10. 人工神经网络理论、设计及应用_TensorFlow深度学习应用实践:教你如何掌握深度学习模型及应用...