【C++知识】重载运算与类型转换

前言

        这一章介绍了各种重载运算和类型转换,知识点有点儿多,大家需要花时间才能记住。需要了解更多知识,建议大家自行查看书籍,这里就只是介绍一些细节问题。

最后,如果有理解不对的·地方,希望大家不吝赐教,谢谢!

【C++系列】【前一章:拷贝控制】【下一章:待续】

十一、重载运算与类型转换

         当运算符作用于类类型的运算对象时,可以通过运算符重载重新定义该运算符的含义。明智地使用运算符重载能令我们的程序更易于编写和阅读。

基本概念

      重载的运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要定义的运算符号共同组成。和其他函数一样,重载的运算符也包含返回类型、参数列表以及函数体。

      参数数量与该运算符作用的运算对象数量一样多,一元运算符有一个参数,二元运算符有两个。

       如果一个运算符函数是成员函数,则它的第一个(左侧)运算对象绑定到隐式的this指针上,因此,成员运算符函数的(显式)参数数量比运算符的对象总数少一个。

         对于一个运算符函数来说,它或者是类的成员,或者至少含有一个类类型的参数。

直接调用一个重载的运算符函数

//一个非成员运算符函数的等价调用
data1+data2;    //普通的表达式
operator+(data1,data2);   //等价的函数调用

像调用其他成员函数一样显式地调用成员运算符函数:

data1+=data2;    //基于“调用”的表达式
data1.operator+=(data2);     //对成员运算符函数的等价调用

某些运算符不应该被重载

       某些运算符指定了运算符对象求值的顺序,因为使用重载的运算符本质上是一次函数调用,所以这些关于运算符求值顺序的规则无法应用到重载的运算符上。特别是:逻辑与运算符、逻辑或运算符和逗号运算符(表达式结果是最右边表达式的结果)的运算对象求值顺序规则无法保留下来。

通常情况下,不应该重载逗号、取地址、逻辑与和逻辑或运算符。

使用与内置类型一致的含义

  • 如果类执行IO操作,则定义移位运算符使其与内置类型的IO保持一致
  • 如果类的某个操作是检查相等性,则定义operator==;如果类有了operator==,意味着它通常也应该有operator!=。
  • 如果类包含一个内在的单序比较操作,则定义operator<;如果类有了operator<,则它也应该含有其他关系操作。
  • 重载运算符的返回类型通常情况下应该与其内置版本的返回类型兼容。

选择作为成员或者非成员

  • 赋值(=)、下标([ ])、调用(( ))和成员访问箭头(->)运算符必须是成员。
  • 复合赋值运算符一般来说应该是成员,但并非必须。
  • 改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员。
  • 具有对称性的运算符可能转换任意一端的运算对象,例如算算术、相等性、关系和位运算符等,因此它们通常应该是普通的非成员函数。

输入和输出运算符

      IO标准库分别使用>>和<<执行输入和输出操作。

重载输出运算符<<

       通常情况下,输出运算符的第一个形参是一个非常量ostream对象的引用。 第二个形参一般来说是一个常量的引用。为了与其他输出运算符保持一致,operator<<一般要返回它的ostream形参。

       通常,输出运算符应该主要负责打印对象的内容而非控制格式,输出运算符不应该打印换行符。

输入输出运算符必须是非成员函数

       假设输入输出运算符是某个类的成员,则它们也应该必须是istream或ostream的成员。然而,这两个类术语标准库,并且我们无法给标准库中的类添加任何成员。当然,IO运算符通常需要读写类的非公有数据成员,所以IO运算符一般被声明为友元。

重载输入运算符>>

       通常情况下,输入运算符的第一个形参是运算符将要读取的流的引用,第二个形参是将要读入到的(非常量)对象的引用。

       输入运算符必须处理输入可能失败的情况,而输出运算符则不需要。

输入时的错误

  • 当流含有错误类型的数据时读取操作可能失败。
  • 当读取操作到达文件末尾或者遇到输入流的其他错误时也会失败。

当读取操作发生错误时,输入运算符应该负责从错误中恢复。

算术和关系运算符

       通常情况下,我们把算术和关系运算符定义成非成员函数以允许对左侧或右侧的运算对象进行转换,因为这些运算符一般不需要改变对象的状态,所以形参都是常量引用。

       如果类同时定义了算术运算符和相关的复合赋值运算符,则通常情况下应该使用复合赋值来实现算术运算符。

相等运算符

设计准则:

  • 如果一个类含有判断两个对象是否相等的操作,则它显然应该把函数定义成operator==而非一个普通的命名函数
  • 如果类定义了operator==,则该运算符应该能判断一组给定的对象中是否含有重复数据。
  • 通常情况下,相等运算符应该具有传递性,换句话说,如果a==b和b==c为真,则a==c也应该为真。
  • 如果类定义了operator==,则这个类也应该定义operator!=。
  • 相等运算符和不相等运算符中的一个应该把工作委托给另一个,这意味着其中一个运算符应该负责实际比较对象的工作,而另一个运算符则只是调用那个真正工作的运算符。

关系运算符

  • 定义顺序关系,令其与关联容器中对关键字的要求一致
  • 如果类同时也含有==运算符的话,则定义一种关系令其与==保持一致。特别是,如果两个对象是!=的,那么一个对象应该<另一个。

如果存在唯一一种逻辑可靠的<定义,则应该考虑为这个类定义<运算符,如果类同时还包含==,则当且仅当<的定义和==产生的结果一致时才定义<运算符。

赋值运算符

       和拷贝赋值及移动赋值运算符一样,其他重载的赋值运算符也必须先释放当前内存空间,再创建一片新空间。

       我们可以重载赋值运算符,不论形参的类型是什么,赋值运算符都必须定义为成员函数。

复合赋值运算符

       复合赋值运算符不非得是类的成员,不过我们还是倾向于把包括复合赋值在内的所有赋值运算都定义在类的内部。返回类型都应该返回左侧运算对象的引用。

 下标运算符

        表示容器的类通常可以通过元素在容器中的位置访问元素,这些类一般会定义下标运算符operator[]。下标运算符必须是成员函数。通常以所访问元素的引用作为返回值,这样做的好处是下标可以出现在赋值运算符的任意一端。

       如果一个类包含下标运算符,则它通常会定义两个版本:一个返回普通引用,另一个是类的常量成员并且返回常量引用。

递增和递减运算符

      定义递增(++)和递减(--)运算符的类应该同时定义前置版本和后置版本。这些运算符通常应该被定义成类的成员。

定义前置递增/递减运算符

     为了与内置版本保持一致,前置运算符应该返回递增或递减对象的引用。

     为了与内置版本保持一致,后置运算符应该返回对象的原值(递增或递减之前的值),返回形式是一个值而非引用。后置版本接受一个额外的(不被使用)int类型的形参。当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参。 

成员访问运算符

解引用运算符(*)和箭头运算符(->)

       箭头运算符不执行任何自己的操作,而是调用解引用运算符并返回解引用结果元素的地址。箭头运算符必须是类的成员,解引用运算符通常也是类的成员,尽管并非必须如此。

       重载的箭头运算符必须返回类的指针或自定义箭头运算符的某个类的对象。

函数调用运算符  operator()

       如果类重载了函数调用运算符,则我们可以像使用函数一样使用该类的对象。因为这样的类同时也能存储状态,所以与普通函数相比它们更加灵活。

       函数调用运算符必须是成员函数,一个类可以定义多个不同版本的调用运算符,相互之间应该在参数数量或类型上有所区别。

含有状态的函数对象类

        和其他类一样,函数对象除了operator()之外也可以包含其他成员。函数对象类通常包含一些数据成员,这些数据成员被用于定制调用运算符中的操作。

可调用对象与function

      C++语言中有几种可调用的对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。调用类型指明了调用返回的类型以及传递给调用的实参类型。一种调用类型形式对应一个函数类型,例如:

int (int,int)  是一个函数类型,它接受两个int,返回一个int

不同类型可能具有相同的调用形式

      我们可能希望使用这些可调用对象构建一个间单的桌面计算器。为了实现这一目的,需要定义一个函数表用于存储指向这些可调用对象的“指针”。当程序需要执行某个特定的操作时,从表中查找该调用的函数。

标准库function类型

      定义在头文件functional中,function是一个模板,当创建一个具体的function类型时我们必须提供额外的信息。

     我们不能(直接)将重载函数的名字存入function类型的对象中。解决二义性问题的一条途径是存储函数指针,而非函数的名字。同样,我们也能使用lambda来消除二义性。

重载、类型转换与运算符

        转换构造函数和类型转换运算符共同定义了类类型转换,这样的转换有时也被称为用户定义的类型转换

类型转换运算符

       类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。类型转换函数的一般形式如下所示:

operator type() const;  其中type表示某种类型,类型转换运算符可以面向任意类型(除了void之外)进行定义,只要该类型能作为函数的返回类型。因此,我们不允许转换成数组或者函数类型,但允许转换成指针(包括数组指针及函数指针)或者引用类型。

       类型转换运算符既没有显式的返回类型,也没有形参,而且必须定义成类的成员函数,类型转换运算符通常不应该改变待转换对象的内容,因此,类型转换运算符一般被定义成const成员。

       一个类型转换函数必须是类的成员函数;它不能声明返回类型,形参列表也必须为空,类型转换函数通常应该是const。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烊萌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值