《C++面向对象高效编程(第2版)》——3.4 赋值操作符
本节书摘来自异步社区出版社《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 赋值操作符相关推荐
- 《C++面向对象高效编程(第2版)》——3.11 类名、成员函数名、参数类型和文档...
本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第3章,第3.11节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...
- 《C++面向对象高效编程(第2版)》——2.29 泛化关系(is-a)
本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第2章,第2.29节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...
- 《C++面向对象高效编程(第2版)》——2.20 什么是多线程安全类
本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第章,第2.20节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区& ...
- 《C++面向对象高效编程(第2版)》——2.30 has-a关系的重要性
本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第2章,第2.30节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...
- 《C++面向对象高效编程(第2版)》——导读
前言 C++面向对象高效编程(第2版) 面向对象软件开发已逐渐成为开发软件的首选.优秀的面向对象软件开发人员.设计人员.系统架构师对其需求与日俱增.要想成为一名成功的面向对象编程(OOP)人员必须忘却 ...
- 《C++面向对象高效编程(第2版)》——3.13 采用语义
本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第3章,第3.13节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...
- 《C++面向对象高效编程(第2版)》——3.17 编写内存安全类
本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第3章,第3.17节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...
- 《C++面向对象高效编程(第2版)》——2.16 识别成员函数的目标对象
本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第2章,第2.26节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...
- 《C++面向对象高效编程(第2版)》——3.16 从函数中返回引用
本节书摘来自异步社区出版社<C++面向对象高效编程(第2版)>一书中的第3章,第3.16节,作者: [美]Kayshav Dattatri,更多章节内容可以访问云栖社区"异步社区 ...
最新文章
- msys2 config.guess: unable to guess system type
- html用a标签怎么提交表单?
- JS - JSON对象与JSON字符串相互转换的几种方法
- flask中数据库的基本操作-增删改查【备忘】
- P2P中的NAT穿越方案简介
- 12.20今日头条实习面试总结-算法工程师
- 创新, FMA amp; SMA 世界第一台VCD机的故事
- flutter跨平台开发之App升级方案
- JAVA中properties基本用法
- 士林变频器面板如何调速度_必读干货丨西威变频器DRIVE OVERLOAD故障处理
- icem合并面网格_详解Flunet Meshing:高级网格划分工具中的“扫地僧”
- 计算机网络期末4小时速成
- 小波 奇异点 matlab,Matlab小波变换对于奇异点的检测.doc
- layout-v21透明主题不生效的bug
- 商场抽奖软件 android,召唤抽奖系统3.0正式版
- 扫描中如何实现自动纠偏
- 区块链技术:未来将颠覆的9大行业
- mysql支持存储emjoy表情
- 办公室日常管理信息系统 数据库课程设计
- 在java中,OOA是什么?OOD是什么?OOP是什么?
热门文章
- 计算机应用与技术网上测试题答案,计算机应用与技术网上测试题.doc
- 浙江大学计算机与机械工程,中国26所“机械工程”大学经调整、合并,浙大、西交、同济升级...
- 事务的四个特性-ACID
- 剑指Offer Ⅱ 003.二进制加法(力扣剑指Offer专项突击版——整数_3)
- 安卓使用Span富文本给某段Text文本加上波浪线
- linux挂载盘符扫描,Linux下挂载ISCSI的盘符问题
- 硕士本科论文通过matlab出仿真图
- matlab缩小矩阵尺寸,Matlab中对矩阵使用矩阵尺寸的索引
- linux内核匹配的编译器gcc,linux下正则匹配的C实现
- 人工神经网络理论、设计及应用_TensorFlow深度学习应用实践:教你如何掌握深度学习模型及应用...