《21天学通C++(第7版)》——12.3 双目运算符

12.3 双目运算符

21天学通C++(第7版)
对两个操作数进行操作的运算符称为双目运算符。以全局函数或静态成员函数的方式实现的双目运算符的定义如下:

以类成员的方式实现的双目运算符的定义如下:

以类成员的方式实现的双目运算符只接受一个参数,其原因是第二个参数通常是从类属性获得的。

12.3.1 双目运算符的类型

表12.2列出了可在C++应用程序中重载或重新定义的双目运算符。

12.3.2 双目加法与双目减法运算符

与递增/递减运算符类似,如果类实现了双目加法和双目减法运算符,便可将其对象加上或减去指定类型的值。再来看看日历类Date,虽然前面实现了将Date递增以便前移一天的功能,但它还不支持增加5天的功能。为实现这种功能,需要实现双目加法运算符,如程序清单12.5中的代码所示。

程序清单12.5 实现了双目加法运算符的日历类

输出:

分析:
第14~25行是双目运算符+和-的实现,让您能够使用简单的加法和减法语法,如main()中的第41和45行所示。

对字符串类来说,双目加法运算符也很有用。第9章分析了简单的字符串包装类MyString,它封装了一个C风格字符串,并提供了内存管理、复制等功能,如程序清单9.9所示。但这个类不支持使用如下语法将两个字符串拼接起来:

不用说,实现运算符+后,MyString使用起来将非常容易,值得去实现它:

程序清单9.9中添加上述代码,并提供实现为空的私有默认构造函数MyString()后,便可使用加法语法了。本章后面的程序清单12.12提供了一个MyString类,它实现了+等运算符。

运算符提高了类的可用性,但实现的运算符必须合理。对于Date类,您实现了加法和减法运算符,但对于MyString类,只实现了加法运算符(+)。这是因为对字符串执行减法运算的可能性极少,实现这样的运算符很可能是在浪费时间。
12.3.3 实现运算符+=与-=
加并赋值运算符支持语法a+=b;,这让程序员可将对象a增加b。这样,程序员可重载加并赋值运算符,使其接受不同类型的参数b。程序清单12.6让您能够给Date对象加上一个整数。

程序清单12.6 定义运算符+=和-=,以便将日历向前或向后翻整型输入参数指定的天数

输出:

分析:
运算符+=和-=是在第14~24行定义的。这些运算符让您能够加上或减去指定的天数,如main()中的下述代码所示:

运算符+=和-=接受一个int参数,让您能够给Date对象加上或减去指定的天数,就像处理的是整数一样。您还可提供运算符+=的重载版本,让它接受一个虚构的CDays对象作为参数:

乘并赋值运算符(*=)、除并赋值运算符(/=)、求模并赋值运算符(%=)、减并赋值运算符(-=)、左移并赋值运算符(<<=)、右移并赋值运算符(>>=)、异或并赋值运算符(^=)、按位或并赋值运算符(|=)以及按位与并赋值运算符(&=)的语法都与程序清单12.6所示的加并赋值运算符类似。

虽然重载运算符的最终目标是让类更直观,更易于使用,但很多时候实现这些运算符并没有意义。例如,前面的日历类Date绝对不会用到按位与并赋值运算符&=。这个类的用户应该不会想通过greatDay &= 20;等操作获得有用的结果。

12.3.4 重载等于运算符(==)和不等运算符(!=)

如果像下面这样将两个Date对象进行比较,结果将如何呢?

由于还没有定义等于运算符,编译器将对这两个对象进行二进制比较,并仅当它们完全相同时才返回true。在有些情况下(包括现在的Date类),这是可行的。然而,如果类有一个非静态字符串成员,它包含字符串值(char *),如程序清单9.9所示的MyString,则比较结果可能不符合预期。在这种情况下,对成员属性进行二进制比较时,实际上将比较字符串指针,而字符串指针并不相等(即使指向的内容相同),因此总是返回false。

因此,正确的做法是定义比较运算符。等于运算符的通用实现如下:

实现不等运算符时,可重用等于运算符:

不等运算符的结果与等于运算符相反(逻辑非)。程序清单12.7列出了日历类Date定义的比较运算符。

程序清单12.7 运算符==和!=

输出:

分析:
等于运算符(==)的实现很简单,它在年、月、日都相同时返回true,如第14~19行所示。实现不等运算符时,重用了等于运算符的代码,如第23行所示。有了这两个运算符后,就可对两个Date对象(Holiday1和Holiday2)进行比较了,如main()中的第42和47行所示。

12.3.5 重载运算符<、>、<=和>=

程序清单12.7所示的代码让Date类足够聪明,能够判断两个Date对象是否相等。然而,如果要使用该类执行类似下面的条件检查,该如何办呢?

如果能够使用这个日历类来比较两个日期,确定哪个在前、哪个在后,将很有用。编写这类的程序员应实现这种比较,让这个类对用户来说尽可能友好和直观,如程序清单12.8所示。

程序清单12.8 实现运算符<、>、<=和>=

输出:

分析:
这里要讨论的运算符是在第21~52行实现的。注意到实现这些运算符时,重用了其他运算符的代码。

在main()函数的第75~84行,使用了这些运算符,以演示这些运算符使得使用Date类简单而直观。

12.3.6 重载复制赋值运算符(=)

有时候,需要将一个类实例的内容赋给另一个类实例,如下所示:

如果您没有提供复制赋值运算符,这将调用编译器自动给类添加的默认复制赋值运算符。根据类的特征,默认复制赋值运算符可能不可行,具体地说是它不复制类管理的资源。与复制构造函数一样,为确保进行深复制,您需要提供复制赋值运算符:

如果类封装了原始指针,如程序清单9.9所示的MyString类,则确保进行深复制很重要。如果没有实现赋值运算符,编译器将提供默认的复制赋值运算符,但它只复制char* Buffer包含的地址,而不复制指向的内存中的内容。这与没有提供复制构造函数时出现的情况相同。为确保赋值时进行深复制,应定义复制赋值运算符,如程序清单12.9所示。

程序清单12.9 对程序清单9.9所示的MyString类进行改进,添加了复制赋值运算符

输出:

分析:
在这个示例中,笔者故意省略了复制构造函数,旨在减少代码行(但您编写这样的类时,应添加它,详情请参阅程序清单9.9)。复制赋值运算符是在第25~39行实现的,其功能与复制构造函数很像。它首先检查源和目标是否同一个对象。如果不是,则释放成员Buffer占用的内存,再重新给它分配足以存储复制源中文本的内存,然后使用strcpy()进行复制,如第36行所示。

相比于程序清单9.9,程序清单12.9的另一个细微差别在于,使用返回const char*的转换运算符替代了函数GetString(),如第53~56行所示。该运算符让MyString类使用起来更容易,如第68行所示——使用一条cout语句显示了两个MyString实例的内容。

如果您编写的类管理着动态分配的资源(如C风格字符串char*)、动态数组等,除构造函数和析构函数外,请务必实现复制构造函数和复制赋值运算符。

如果没有考虑对象被复制时出现的资源所有权问题,您的类就是不完整的,使用时甚至会有危险。

要创建不允许复制的类,可将复制构造函数和复制赋值运算符都声明为私有的。只需这样声明(甚至都不用提供实现)就足以让编译器在遇到试图复制对象(将对象按值传递给函数或将一个对象赋给另一个对象)的代码时引发错误。

12.3.7 下标运算符

下标运算符让您能够像访问数组那样访问类,其典型语法如下:

编写封装了动态数组的类(如封装了char* Buffer的MyString)时,通过实现下标运算符,可轻松地随机访问缓冲区中的各个字符:

程序清单12.10是一个简单的示例,演示了下标运算符([])让用户能够使用常规数组语法来遍历MyString实例包含的字符。

程序清单12.10 在MyString类中实现下标运算符,以便随机访问MyString::Buffer包含的字符

输出:

分析:
这个程序很有趣,它接受用户输入的句子,并使用它创建一个MyString对象,如第61行所示;接下来,在一个for循环中,使用下标运算符([])和数组语法逐字符地打印该字符串,如第64~65行所示。下标运算符([])是在第31~35行实现的,它首先确保指定的位置没有超出char*Buffer末尾,然后返回指定位置处的字符。

实现运算符时,应使用关键字const,这很重要。在程序清单12.10中,将下标运算符([])的返回类型声明成了const char&。即便没有关键字const,该程序也能通过编译。这里使用它旨在禁止使用下面这样的代码:

通过使用const,可禁止从外部通过运算符[]直接修改成员MyString::Buffer。除将返回类型声明为const外,还将该运算符的函数类型设置成为const,这将禁止该运算符修改类的成员属性。

一般而言,应尽可能使用const,以免无意间修改数据,并最大限度地保护类的成员属性。
实现下标运算符时,可在程序清单12.10所示版本的基础上进行改进。这个版本只实现了一个下标运算符,它可用于读写动态数组的元素。

然而,也可实现两个下标运算符,其中一个为const函数,另一个为非const函数:

编译器很聪明,能够在读取MyString对象时调用const函数,而在对MyString执行写入操作时调用非const函数。因此,如果愿意,可在两个下标函数中实现不同的功能。例如,一个运算符记录对容器的写入操作,而另一个记录对容器的读取操作。还有其他双目运算符可被重定义或重载(如表12.2所示),但本章不打算介绍它们。这些运算符的实现与已讨论的运算符类似。

如果其他运算符(如逻辑运算符和按位运算符)有助于改善您编写的类,就应实现它们。显然,诸如Date等日历类没有必要实现逻辑运算符,但处理字符串和数字的类可能需要实现它们。

应根据类的目标和用途重载运算符或实现新的运算符。

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

时间: 2024-07-30 08:26:21

《21天学通C++(第7版)》——12.3 双目运算符的相关文章

《21天学通C语言(第6版•修订版)》一导读

前 言 21天学通C语言(第6版•修订版) 本书旨在引导读者在21天内学通C语言编程.虽然有来自诸如C++.Java和C#的激烈竞争,但很多初学编程者还是会选择C语言.正如第1天课程介绍的原因,选择C语言可确保您不会误入"歧途". 将本书作为自学C语言的教材是一个明智的决定.虽然市面上有很多有关C语言的图书,但本书介绍C语言的方式最为合理,也让读者学习起来最为容易.本书的前五版都登上了畅销书排行榜,这一事实表明我们的观点得到了读者的认同.本书是按读者每天阅读一章的方式编写的.读者不需要

《21天学通C语言(第6版•修订版)》一1.2 为何要使用C语言

1.2 为何要使用C语言 21天学通C语言(第6版•修订版) 在当前的计算机编程领域中,有大量的高级语言可供选择,如C.Perl.BASIC.Java和C#.这些都是非常卓越的语言,适合用于完成大部分编程任务.虽然如此,但基于以下几个原因,很多计算机专业人员认为C语言是其中最佳的: C语言功能强大.灵活.使用C语言能够完成的工作只受限于您的想象力,语言本身不会给您带来任何约束.C语言可用于完成操作系统.字处理器.图形.电子表格等项目,甚至可用于编写其他语言的编译器. C语言很流行,是专业程序员的

《21天学通C语言(第7版)》一导读

前言 21天学通C语言(第7版) 从书名便可看出,通过学习本书,你可以自学C程序设计语言.在众多语言(如C++.JAVA和C#)中,C仍然是学习程序设计语言的首选.第1课中将详细介绍其中的原因.选择C作为程序设计语言是明智之举. 与市面上其他C语言的书籍相比,本书的讲解逻辑更清晰,初学者更容易理解.之前的6个版本一直在畅销书排行榜上遥遥领先,广受读者赞誉!本书为读者量身定制,每天只需花一小时便可学完一课内容.读者不需要有任何编程经验,当然,如果有其他语言的基础(如BASIC),学起来会更快.本书

《21天学通C++(第7版)》导读

前言 21天学通C++(第7版) 对C++来说,2011是个很特别的年份.在这一年,C++11终于获批成为新标准,它新增了一些可提高编程效率的关键字和结构,让您能够编写更优质的代码.本书旨在帮助您循序渐进地学习C++11,其中的章节经过仔细编排,从实用的角度介绍这种面向对象的编程语言的基本知识.读者只需每天花1小时,在学完本书后,就能掌握C++11. 学习C++的最佳方式是动手实践.本书包含丰富的代码示例,有助于读者提高编程技能,请务必亲自动手尝试这些代码.这些代码片段都使用了(在本书编写时)最

《21天学通C++(第7版)》——17.2 典型的vector操作

17.2 典型的vector操作 21天学通C++(第7版) std::vector类的行为规范和公有成员是由C++标准定义的,因此,遵循该标准的所有C++编程平台都支持本章将介绍的vector操作. 17.2.1 实例化vector vector是一个模板类,需要使用第14章介绍的方法进行实例化.要实例化vector,需要指定要在该动态数组中存储的对象类型: 要声明指向list中元素的迭代器,可以这样做: 如果需要可用于修改值或调用非const函数的迭代器,可使用iterator代替const

《21天学通C++(第7版)》——17.6 问与答

17.6 问与答 21天学通C++(第7版) **问:vector会改变其存储的元素的顺序吗? 答:**vector是一种顺序容器,元素的存储顺序与插入顺序相同. **问:要将元素插入到vector中,应使用哪个函数?元素将插入到vector的什么位置? 答:**成员函数push_back将元素插入到vector末尾. **问:哪个函数用于获悉存储在vector中的元素个数? 答:**成员函数size ()返回存储在vector中的元素个数.对于所有STL容器,该函数都如此. 问:随着vecto

《21天学通Java(第6版)》—— 1.5 组织类和类行为

1.5 组织类和类行为 21天学通Java(第6版) Java面向对象编程还涉及另外三个概念:继承.接口和包,这些都是用于组织类和类行为的机制. 1.5.1 继承 继承是面向对象编程中最重要的概念之一,直接影响您如何设计和编写Java类. 继承是一种机制,让一个类能够继承另一个类的所有行为和属性. 通过继承,一个类可自动拥有现有类的所有功能,因此只需定义与现有类不同的地方. 通过继承,所有的类(无论是您创建的类,还是Java类库中的类)都以严格的层次结构来组织. 继承其他类的类叫子类,被继承的类

《21天学通C语言(第7版)》一6.2 控制程序的执行

6.2 控制程序的执行 21天学通C语言(第7版) C程序默认的执行顺序是自上而下.从main()函数的起始位置开始,逐条执行语句,直至main()函数的末尾.然而,在实际的C程序中,很少严格按这样的顺序执行.C语言提供了各种程序控制语句,方便程序员控制程序的执行顺序.第4课介绍了一种程序控制语句--if语句,接下来介绍另外3种有用的控制语句: for语句: while语句: do...while语句. 6.2.1 for语句 for语句是由一条或多条语句组成的块.for语句有时也被称为for循

《21天学通Java(第6版)》—— 2.5 表达式和运算符

2.5 表达式和运算符 21天学通Java(第6版) 表达式是一条能够提供值的语句.最常见的是数学表达式,如下面的例子所示: 这3条语句都是表达式-它们提供了可被赋给变量的值.第1条语句将字面量3赋给变量x.第2条语句将变量x的值赋给变量y.在第3条语句中,乘法运算符*用来将x和y相乘,结果存储在变量z中. 表达式可以是任何变量.字面量和运算符的组合,也可以是方法调用,因为方法能够将一个值返回给调用它的类或对象. 您知道,表达式所提供的值称为返回值.在Java程序中,可将这个值赋给变量或以其他方

《21天学通Java(第6版)》—— 导读

前言 21天学通Java(第6版) 有些革命出其不意地吸引了全世界的眼球.Twitter.Linux操作系统和电视剧<Cupcake Wars>的异军突起颠覆了传统思维模式. 而Java语言的巨大成功却在人们的意料之中.自从Java语言于17年前面世以来,人们就对它充满殷切的期望.当Java融入到Web浏览器时,公众以无比的热情欢迎这种新语言的到来. Sun公司创始人Bill Joy在介绍这种新语言时,毫不掩饰其孤注一掷的心态:"15年来,我们一直力图开发出一种更佳的编程语言和环境,